Merge change 26281 into eclair
* changes:
New text field to display the punched numbers.
diff --git a/res/layout-finger/fasttrack_item.xml b/res/layout-finger/fasttrack_item.xml
index e820a03..58d85aa 100644
--- a/res/layout-finger/fasttrack_item.xml
+++ b/res/layout-finger/fasttrack_item.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<com.android.contacts.ui.CheckableImageView
+<com.android.contacts.ui.widget.CheckableImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="59dip"
android:layout_height="52dip"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4de02ba..f358592 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -887,47 +887,25 @@
<!-- Toast indicating that sharing a contact has failed. -->
<string name="share_error">This contact cannot be shared.</string>
+ <!-- Header that expands to list all name types when editing a structured name of a contact -->
+ <string name="nameLabelsGroup">Name</string>
+ <!-- Header that expands to list all nickname types when editing a nickname of a contact -->
+ <string name="nicknameLabelsGroup">Nickname</string>
+ <!-- Header that expands to list all organization types when editing an organization of a contact -->
+ <string name="organizationLabelsGroup">Organization</string>
+ <!-- Header that expands to list all website types when editing a website of a contact -->
+ <string name="websiteLabelsGroup">Website</string>
-
-<!-- TODO: add comments to each of these strings to prepare for translation -->
-<string name="nameLabelsGroup">Name</string>
-<string name="nicknameLabelsGroup">Nickname</string>
-<string name="organizationLabelsGroup">Organization</string>
-<string name="websiteLabelsGroup">Website</string>
-
-<string name="type_home">Home</string>
-<string name="type_mobile">Mobile</string>
-<string name="type_work">Work</string>
-<string name="type_fax_work">Work Fax</string>
-<string name="type_fax_home">Home Fax</string>
-<string name="type_pager">Pager</string>
-<string name="type_other">Other</string>
-<string name="type_custom">Custom</string>
-
-<!-- Single-character overlays on shortcut icons -->
-<string name="type_short_home">H</string>
-<string name="type_short_mobile">M</string>
-<string name="type_short_work">W</string>
-<string name="type_short_pager">P</string>
-<string name="type_short_other">O</string>
-
-<!-- exchange specific -->
-<string name="type_home_2">Home 2</string>
-<string name="type_work_2">Work 2</string>
-<string name="type_car">Car</string>
-<string name="type_company_main">Company Main</string>
-<string name="type_mms">MMS</string>
-<string name="type_radio">Radio</string>
-<string name="type_assistant">Assistant</string>
-
-<string name="type_im_aim">AIM</string>
-<string name="type_im_msn">Windows Live</string>
-<string name="type_im_yahoo">Yahoo</string>
-<string name="type_im_skype">Skype</string>
-<string name="type_im_qq">QQ</string>
-<string name="type_im_google_talk">Google Talk</string>
-<string name="type_im_icq">ICQ</string>
-<string name="type_im_jabber">Jabber</string>
+ <!-- Single-character overlay for home phone numbers when creating desktop shortcuts -->
+ <string name="type_short_home">H</string>
+ <!-- Single-character overlay for mobile phone numbers when creating desktop shortcuts -->
+ <string name="type_short_mobile">M</string>
+ <!-- Single-character overlay for work phone numbers when creating desktop shortcuts -->
+ <string name="type_short_work">W</string>
+ <!-- Single-character overlay for pager phone numbers when creating desktop shortcuts -->
+ <string name="type_short_pager">P</string>
+ <!-- Single-character overlay for other phone numbers when creating desktop shortcuts -->
+ <string name="type_short_other">O</string>
<!-- Shown as the header title over a collapsible section that, by default, hides
secondary contact detail edit fields, such as birthday. -->
@@ -957,89 +935,165 @@
user to give a specific name to describe this address. -->
<string name="label_email_display_name">Display name</string>
-<string name="call_home">Call home</string>
-<string name="call_mobile">Call mobile</string>
-<string name="call_work">Call work</string>
-<string name="call_fax_work">Call work fax</string>
-<string name="call_fax_home">Call home fax</string>
-<string name="call_pager">Call pager</string>
-<string name="call_other">Call other</string>
-<string name="call_custom">Call <xliff:g id="custom">%s</xliff:g></string>
+ <!-- Action string for calling a custom phone number -->
+ <string name="call_custom">Call <xliff:g id="custom">%s</xliff:g></string>
+ <!-- Action string for calling a home phone number -->
+ <string name="call_home">Call home</string>
+ <!-- Action string for calling a mobile phone number -->
+ <string name="call_mobile">Call mobile</string>
+ <!-- Action string for calling a work phone number -->
+ <string name="call_work">Call work</string>
+ <!-- Action string for calling a work fax phone number -->
+ <string name="call_fax_work">Call work fax</string>
+ <!-- Action string for calling a home fax phone number -->
+ <string name="call_fax_home">Call home fax</string>
+ <!-- Action string for calling a pager phone number -->
+ <string name="call_pager">Call pager</string>
+ <!-- Action string for calling an other phone number -->
+ <string name="call_other">Call other</string>
+ <!-- Action string for calling a callback number -->
+ <string name="call_callback">Call callback</string>
+ <!-- Action string for calling a car phone number -->
+ <string name="call_car">Call car</string>
+ <!-- Action string for calling a company main phone number -->
+ <string name="call_company_main">Call company main</string>
+ <!-- Action string for calling a ISDN phone number -->
+ <string name="call_isdn">Call ISDN</string>
+ <!-- Action string for calling a main phone number -->
+ <string name="call_main">Call main</string>
+ <!-- Action string for calling an other fax phone number -->
+ <string name="call_other_fax">Call other fax</string>
+ <!-- Action string for calling a radio phone number -->
+ <string name="call_radio">Call radio</string>
+ <!-- Action string for calling a Telex phone number -->
+ <string name="call_telex">Call telex</string>
+ <!-- Action string for calling a TTY/TDD phone number -->
+ <string name="call_tty_tdd">Call TTY/TDD</string>
+ <!-- Action string for calling a work mobile phone number -->
+ <string name="call_work_mobile">Call work mobile</string>
+ <!-- Action string for calling a work pager phone number -->
+ <string name="call_work_pager">Call work pager</string>
+ <!-- Action string for calling an assistant phone number -->
+ <string name="call_assistant">Call <xliff:g id="assistant">%s</xliff:g></string>
+ <!-- Action string for calling a MMS phone number -->
+ <string name="call_mms">Call MMS</string>
-<!-- exchange specific -->
-<string name="call_home_2">Call home 2</string>
-<string name="call_work_2">Call work 2</string>
-<string name="call_car">Call car</string>
-<string name="call_company_main">Call company main</string>
-<string name="call_mms">Call MMS</string>
-<string name="call_radio">Call radio</string>
+ <!-- Action string for sending an SMS to a custom phone number -->
+ <string name="sms_custom">Text <xliff:g id="custom">%s</xliff:g></string>
+ <!-- Action string for sending an SMS to a home phone number -->
+ <string name="sms_home">Text home</string>
+ <!-- Action string for sending an SMS to a mobile phone number -->
+ <string name="sms_mobile">Text mobile</string>
+ <!-- Action string for sending an SMS to a work phone number -->
+ <string name="sms_work">Text work</string>
+ <!-- Action string for sending an SMS to a work fax phone number -->
+ <string name="sms_fax_work">Text work fax</string>
+ <!-- Action string for sending an SMS to a home fax phone number -->
+ <string name="sms_fax_home">Text home fax</string>
+ <!-- Action string for sending an SMS to a pager phone number -->
+ <string name="sms_pager">Text pager</string>
+ <!-- Action string for sending an SMS to an other phone number -->
+ <string name="sms_other">Text other</string>
+ <!-- Action string for sending an SMS to a callback number -->
+ <string name="sms_callback">Text callback</string>
+ <!-- Action string for sending an SMS to a car phone number -->
+ <string name="sms_car">Text car</string>
+ <!-- Action string for sending an SMS to a company main phone number -->
+ <string name="sms_company_main">Text company main</string>
+ <!-- Action string for sending an SMS to a ISDN phone number -->
+ <string name="sms_isdn">Text ISDN</string>
+ <!-- Action string for sending an SMS to a main phone number -->
+ <string name="sms_main">Text main</string>
+ <!-- Action string for sending an SMS to an other fax phone number -->
+ <string name="sms_other_fax">Text other fax</string>
+ <!-- Action string for sending an SMS to a radio phone number -->
+ <string name="sms_radio">Text radio</string>
+ <!-- Action string for sending an SMS to a Telex phone number -->
+ <string name="sms_telex">Text telex</string>
+ <!-- Action string for sending an SMS to a TTY/TDD phone number -->
+ <string name="sms_tty_tdd">Text TTY/TDD</string>
+ <!-- Action string for sending an SMS to a work mobile phone number -->
+ <string name="sms_work_mobile">Text work mobile</string>
+ <!-- Action string for sending an SMS to a work pager phone number -->
+ <string name="sms_work_pager">Text work pager</string>
+ <!-- Action string for sending an SMS to an assistant phone number -->
+ <string name="sms_assistant">Text <xliff:g id="assistant">%s</xliff:g></string>
+ <!-- Action string for sending an SMS to a MMS phone number -->
+ <string name="sms_mms">Text MMS</string>
+ <!-- Action string for sending an email to a home email address -->
+ <string name="email_home">Email home</string>
+ <!-- Action string for sending an email to a mobile email address -->
+ <string name="email_mobile">Email mobile</string>
+ <!-- Action string for sending an email to a work email address -->
+ <string name="email_work">Email work</string>
+ <!-- Action string for sending an email to an other email address -->
+ <string name="email_other">Email other</string>
+ <!-- Action string for sending an email to a custom email address -->
+ <string name="email_custom">Email <xliff:g id="custom">%s</xliff:g></string>
+ <!-- Generic action string for sending an email -->
+ <string name="email">Email</string>
+ <!-- Action string for viewing a home postal address -->
+ <string name="map_home">View home address</string>
+ <!-- Action string for viewing a work postal address -->
+ <string name="map_work">View work address</string>
+ <!-- Action string for viewing an other postal address -->
+ <string name="map_other">View other address</string>
+ <!-- Action string for viewing a custom postal address -->
+ <string name="map_custom">View <xliff:g id="custom">%s</xliff:g> address</string>
+ <!-- Action string for starting an IM chat with the AIM protocol -->
+ <string name="chat_aim">Chat using AIM</string>
+ <!-- Action string for starting an IM chat with the MSN or Windows Live protocol -->
+ <string name="chat_msn">Chat using Windows Live</string>
+ <!-- Action string for starting an IM chat with the Yahoo protocol -->
+ <string name="chat_yahoo">Chat using Yahoo</string>
+ <!-- Action string for starting an IM chat with the Skype protocol -->
+ <string name="chat_skype">Chat using Skype</string>
+ <!-- Action string for starting an IM chat with the QQ protocol -->
+ <string name="chat_qq">Chat using QQ</string>
+ <!-- Action string for starting an IM chat with the Google Talk protocol -->
+ <string name="chat_gtalk">Chat using Google Talk</string>
+ <!-- Action string for starting an IM chat with the ICQ protocol -->
+ <string name="chat_icq">Chat using ICQ</string>
+ <!-- Action string for starting an IM chat with the Jabber protocol -->
+ <string name="chat_jabber">Chat using Jabber</string>
-<string name="sms_home">Text home</string>
-<string name="sms_mobile">Text mobile</string>
-<string name="sms_work">Text work</string>
-<string name="sms_fax_work">Text work fax</string>
-<string name="sms_fax_home">Text home fax</string>
-<string name="sms_pager">Text pager</string>
-<string name="sms_other">Text other</string>
-<string name="sms_custom">Text <xliff:g id="custom">%s</xliff:g></string>
+ <!-- Generic action string for starting an IM chat -->
+ <string name="chat">Chat</string>
-<!-- exchange specific -->
-<string name="sms_home_2">Text home 2</string>
-<string name="sms_work_2">Text work 2</string>
-<string name="sms_car">Text car</string>
-<string name="sms_company_main">Text company main</string>
-<string name="sms_mms">Text MMS</string>
-<string name="sms_radio">Text radio</string>
+ <!-- Field title for the street of a structured postal address of a contact -->
+ <string name="postal_street">Street</string>
+ <!-- Field title for the PO box of a structured postal address of a contact -->
+ <string name="postal_pobox">PO box</string>
+ <!-- Field title for the neighborhood of a structured postal address of a contact -->
+ <string name="postal_neighborhood">Neighborhood</string>
+ <!-- Field title for the city of a structured postal address of a contact -->
+ <string name="postal_city">City</string>
+ <!-- Field title for the region, or state, of a structured postal address of a contact -->
+ <string name="postal_region">State</string>
+ <!-- Field title for the postal code of a structured postal address of a contact -->
+ <string name="postal_postcode">ZIP code</string>
+ <!-- Field title for the country of a structured postal address of a contact -->
+ <string name="postal_country">Country</string>
-
-
-<string name="email_home">Email home</string>
-<string name="email_mobile">Email mobile</string>
-<string name="email_work">Email work</string>
-<string name="email_other">Email other</string>
-<string name="email_custom">Email <xliff:g id="custom">%s</xliff:g></string>
-
-<string name="email">Email</string>
-
-
-
-<string name="map_home">View home address</string>
-<string name="map_work">View work address</string>
-<string name="map_other">View other address</string>
-<string name="map_custom">View <xliff:g id="custom">%s</xliff:g> address</string>
-
-<string name="chat_aim">Chat using AIM</string>
-<string name="chat_msn">Chat using Windows Live</string>
-<string name="chat_yahoo">Chat using Yahoo</string>
-<string name="chat_skype">Chat using Skype</string>
-<string name="chat_qq">Chat using QQ</string>
-<string name="chat_gtalk">Chat using Google Talk</string>
-<string name="chat_icq">Chat using ICQ</string>
-<string name="chat_jabber">Chat using Jabber</string>
-<string name="chat_other">Chat</string>
-
-<string name="postal_street">Street</string>
-<string name="postal_pobox">PO box</string>
-<string name="postal_neighborhood">Neighborhood</string>
-<string name="postal_city">City</string>
-<string name="postal_region">State</string>
-<string name="postal_postcode">ZIP code</string>
-<string name="postal_country">Country</string>
-
-
-<string name="name_given">Given name</string>
-<string name="name_family">Family name</string>
-<string name="name_prefix">Name prefix</string>
-<string name="name_middle">Middle name</string>
-<string name="name_suffix">Name suffix</string>
-<string name="name_phonetic_given">Phonetic given name</string>
-<string name="name_phonetic_middle">Phonetic middle name</string>
-<string name="name_phonetic_family">Phonetic family name</string>
-
-
+ <!-- Field title for the given name of a contact -->
+ <string name="name_given">Given name</string>
+ <!-- Field title for the family name of a contact -->
+ <string name="name_family">Family name</string>
+ <!-- Field title for the prefix name of a contact -->
+ <string name="name_prefix">Name prefix</string>
+ <!-- Field title for the middle name of a contact -->
+ <string name="name_middle">Middle name</string>
+ <!-- Field title for the suffix name of a contact -->
+ <string name="name_suffix">Name suffix</string>
+ <!-- Field title for the phonetic given name of a contact -->
+ <string name="name_phonetic_given">Phonetic given name</string>
+ <!-- Field title for the phonetic middle name of a contact -->
+ <string name="name_phonetic_middle">Phonetic middle name</string>
+ <!-- Field title for the phonetic family name of a contact -->
+ <string name="name_phonetic_family">Phonetic family name</string>
</resources>
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 125701b..5d47ee4 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -23,6 +23,7 @@
import com.android.contacts.ui.DisplayGroupsActivity;
import com.android.contacts.ui.DisplayGroupsActivity.Prefs;
import com.android.contacts.util.Constants;
+import com.google.android.collect.Lists;
import android.accounts.Account;
import android.app.Activity;
@@ -38,6 +39,7 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.UriMatcher;
import android.content.res.Resources;
import android.database.CharArrayBuffer;
import android.database.Cursor;
@@ -50,6 +52,7 @@
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.Message;
@@ -348,6 +351,14 @@
private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
private static final String CLAUSE_ONLY_PHONES = Contacts.HAS_PHONE_NUMBER + "=1";
+ // Uri matcher for contact id
+ private static final int CONTACTS_ID = 1001;
+ private static final UriMatcher CONTACTS_ID_MATCHER;
+ static {
+ CONTACTS_ID_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+ CONTACTS_ID_MATCHER.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
+ }
+
private class DeleteClickListener implements DialogInterface.OnClickListener {
private Uri mUri;
@@ -484,12 +495,26 @@
// 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 (CONTACTS_ID_MATCHER.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))) {
- newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
+ 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, intent.getData());
+ newIntent = new Intent(Intent.ACTION_VIEW, data);
}
startActivity(newIntent);
finish();
@@ -554,11 +579,7 @@
// We manually save/restore the listview state
list.setSaveEnabled(false);
- if (mMode == MODE_JOIN_CONTACT) {
- mQueryHandler = new SuggestionsQueryHandler(this, mQueryAggregateId);
- } else {
- mQueryHandler = new QueryHandler(this);
- }
+ mQueryHandler = new QueryHandler(this);
mJustCreated = true;
// TODO(jham) redesign this
@@ -1543,6 +1564,17 @@
}
}
+ 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 static String getSortOrder(String[] projectionType) {
/* if (Locale.getDefault().equals(Locale.JAPAN) &&
projectionType == AGGREGATES_PRIMARY_PHONE_PROJECTION) {
@@ -1559,6 +1591,7 @@
// Cancel any pending queries
mQueryHandler.cancelOperation(QUERY_TOKEN);
+ mQueryHandler.setLoadingJoinSuggestions(false);
String[] projection = getProjectionForQuery();
Uri uri = getUriToQuery();
@@ -1638,12 +1671,8 @@
break;
case MODE_JOIN_CONTACT:
- Uri suggestionsUri = Contacts.CONTENT_URI.buildUpon()
- .appendEncodedPath(String.valueOf(mQueryAggregateId))
- .appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY)
- .appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS))
- .build();
- mQueryHandler.startQuery(QUERY_TOKEN, null, suggestionsUri, projection,
+ mQueryHandler.setLoadingJoinSuggestions(true);
+ mQueryHandler.startQuery(QUERY_TOKEN, null, getJoinSuggestionsUri(null), projection,
null, null, null);
break;
}
@@ -1710,6 +1739,17 @@
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);
+ return resolver.query(getContactFilterUri(filter), projection,
+ getContactSelection(), null, getSortOrder(projection));
}
}
throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
@@ -1821,16 +1861,39 @@
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 {
+ activity.mAdapter.setSuggestionsCursor(null);
+ }
+
+ startQuery(QUERY_TOKEN, null, activity.getContactFilterUri(activity.mQuery),
+ CONTACTS_SUMMARY_PROJECTION,
+ Contacts._ID + " != " + activity.mQueryAggregateId, null,
+ getSortOrder(CONTACTS_SUMMARY_PROJECTION));
+ return;
+ }
+
activity.mAdapter.setLoading(false);
activity.getListView().clearTextFilter();
activity.mAdapter.changeCursor(cursor);
@@ -1850,46 +1913,6 @@
}
}
- /**
- * Query handler for the suggestions query used in the Join Contacts UI. Once the
- * suggestions query is complete, the handler launches an A-Z query. The entire search is only
- * done once the second query is complete.
- */
- private static final class SuggestionsQueryHandler extends QueryHandler {
- boolean mSuggestionsQueryComplete;
- private final long mAggregateId;
-
- public SuggestionsQueryHandler(Context context, long aggregateId) {
- super(context);
- mAggregateId = aggregateId;
- }
-
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- if (mSuggestionsQueryComplete) {
- super.onQueryComplete(token, cookie, cursor);
- return;
- }
-
- mSuggestionsQueryComplete = true;
-
- final ContactsListActivity activity = mActivity.get();
- if (activity != null && !activity.isFinishing()) {
- if (cursor.getCount() > 0) {
- activity.mAdapter.setSuggestionsCursor(cursor);
- } else {
- activity.mAdapter.setSuggestionsCursor(null);
- }
- startQuery(QUERY_TOKEN, null, Contacts.CONTENT_URI, CONTACTS_SUMMARY_PROJECTION,
- Contacts._ID + " != " + mAggregateId, null,
- getSortOrder(CONTACTS_SUMMARY_PROJECTION));
-
- } else {
- cursor.close();
- }
- }
- }
-
final static class ContactListItemCache {
public TextView header;
public View divider;
@@ -1923,7 +1946,6 @@
private String mAlphabet;
private boolean mLoading = true;
private CharSequence mUnknownNameText;
- private SparseArray<Integer> mLocalizedLabels;
private boolean mDisplayPhotos = false;
private boolean mDisplayCallButton = false;
private boolean mDisplayAdditionalData = true;
@@ -1947,19 +1969,13 @@
switch (mMode) {
case MODE_LEGACY_PICK_POSTAL:
case MODE_PICK_POSTAL:
- mLocalizedLabels = inflateLocalizedLabels(
- CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE);
mDisplaySectionHeaders = false;
break;
case MODE_LEGACY_PICK_PHONE:
case MODE_PICK_PHONE:
mDisplaySectionHeaders = false;
- mLocalizedLabels = inflateLocalizedLabels(
- CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
break;
default:
- mLocalizedLabels = inflateLocalizedLabels(
- CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
break;
}
@@ -1991,29 +2007,6 @@
}
}
- private SparseArray<Integer> inflateLocalizedLabels(String mimetype) {
- SparseArray<Integer> localizedLabels = new SparseArray<Integer>();
-
- Sources sources = Sources.getInstance(ContactsListActivity.this);
-
- ContactsSource contactsSource = sources.getInflatedSource(null /*get fallback type*/,
- ContactsSource.LEVEL_MIMETYPES);
- if (contactsSource == null) {
- return localizedLabels;
- }
-
- DataKind kind = contactsSource.getKindForMimetype(mimetype);
- if (kind == null) {
- return localizedLabels;
- }
-
- for (EditType type : kind.typeList) {
- localizedLabels.put(type.rawValue, type.labelRes);
- }
-
- return localizedLabels;
- }
-
private class ImageFetchHandler extends Handler {
@Override
@@ -2317,7 +2310,7 @@
viewToUse.setImageResource(R.drawable.ic_contact_list_picture);
// Add it to a set of images that are populated asynchronously.
- mItemsMissingImages.add(cache.photoView);
+ mItemsMissingImages.add(viewToUse);
if (mScrollState != OnScrollListener.SCROLL_STATE_FLING) {
@@ -2349,18 +2342,15 @@
// Set the label.
if (!cursor.isNull(typeColumnIndex)) {
labelView.setVisibility(View.VISIBLE);
- int type = cursor.getInt(typeColumnIndex);
- if (type != CommonDataKinds.BaseTypes.TYPE_CUSTOM) {
- try {
- labelView.setText(mLocalizedLabels.get(type));
- } catch (ArrayIndexOutOfBoundsException e) {
- labelView.setText(mLocalizedLabels.get(defaultType));
- }
+ final int type = cursor.getInt(typeColumnIndex);
+ final String label = cursor.getString(labelColumnIndex);
+
+ if (mMode == MODE_LEGACY_PICK_POSTAL || mMode == MODE_PICK_POSTAL) {
+ labelView.setText(StructuredPostal.getTypeLabel(context.getResources(), type,
+ label));
} else {
- cursor.copyStringToBuffer(labelColumnIndex, cache.labelBuffer);
- // Don't check size, if it's zero just don't show anything
- labelView.setText(cache.labelBuffer.data, 0, cache.labelBuffer.sizeCopied);
+ labelView.setText(Phone.getTypeLabel(context.getResources(), type, label));
}
} else {
// There is no label, hide the the view
@@ -2405,6 +2395,7 @@
@Override
public void changeCursor(Cursor cursor) {
+
// Get the split between starred and frequent items, if the mode is strequent
mFrequentSeparatorPos = ListView.INVALID_POSITION;
if (cursor != null && cursor.getCount() > 0 && mMode == MODE_STREQUENT) {
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index dea0bad..13a17c1 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -339,17 +339,7 @@
public static View createTabIndicatorView(ViewGroup parent, ContactsSource source) {
Drawable icon = null;
if (source != null) {
- final String packageName = source.resPackageName;
- if (source.iconRes > 0) {
- try {
- final Context authContext = parent.getContext().
- createPackageContext(packageName, 0);
- icon = authContext.getResources().getDrawable(source.iconRes);
-
- } catch (PackageManager.NameNotFoundException e) {
- Log.d(TAG, "error getting the Package Context for " + packageName, e);
- }
- }
+ icon = source.getDisplayIcon(parent.getContext());
}
return createTabIndicatorView(parent, null, icon);
}
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index a3d5de5..027c70e 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -264,7 +264,7 @@
mWakeLock.release();
mProgressDialogForReadVCard.dismiss();
// finish() is called via ErrorDisplayer() on failure.
- if (shouldCallFinish) {
+ if (shouldCallFinish && !isFinishing()) {
if (mErrorFileNameList == null || mErrorFileNameList.isEmpty()) {
finish();
} else {
@@ -789,7 +789,23 @@
super.onStop();
if (mVCardReadThread != null) {
// The Activity is no longer visible. Stop the thread.
- // TODO: The Activity may be destroyed without this method being called.
+ mVCardReadThread.cancel();
+ mVCardReadThread = null;
+ }
+
+ // ImportVCardActivity should not be persistent. In other words, if there's some
+ // event calling onStop(), this Activity should finish its work and give the main
+ // screen back to the caller Activity.
+ if (!isFinishing()) {
+ finish();
+ }
+ }
+
+ @Override
+ public void finalize() {
+ if (mVCardReadThread != null) {
+ // Not sure this procedure is really needed, but just in case...
+ Log.w(LOG_TAG, "VCardReadThread exists while this Activity is now being killed!");
mVCardReadThread.cancel();
mVCardReadThread = null;
}
diff --git a/src/com/android/contacts/SplitAggregateView.java b/src/com/android/contacts/SplitAggregateView.java
index 09cf5ec..ed78fd9 100644
--- a/src/com/android/contacts/SplitAggregateView.java
+++ b/src/com/android/contacts/SplitAggregateView.java
@@ -251,16 +251,7 @@
ContactsSource source = mSources.getInflatedSource(info.accountType,
ContactsSource.LEVEL_SUMMARY);
if (source != null) {
- final String packageName = source.resPackageName;
- if (source.iconRes > 0) {
- try {
- final Context context = getContext().createPackageContext(packageName, 0);
- icon = context.getResources().getDrawable(source.iconRes);
-
- } catch (PackageManager.NameNotFoundException e) {
- Log.d(TAG, "error getting the Package Context for " + packageName, e);
- }
- }
+ icon = source.getDisplayIcon(getContext());
}
if (icon != null) {
cache.sourceIcon.setImageDrawable(icon);
diff --git a/src/com/android/contacts/StyleManager.java b/src/com/android/contacts/StyleManager.java
deleted file mode 100644
index 2d24551..0000000
--- a/src/com/android/contacts/StyleManager.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2009 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.model.Sources;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.WeakHashMap;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-
-
-/**
- * @deprecated Use {@link Sources} instead.
- */
-@Deprecated
-public final class StyleManager extends BroadcastReceiver {
-
- public static final String TAG = "StyleManager";
-
- private static StyleManager sInstance = null;
-
- private WeakHashMap<String, Bitmap> mIconCache;
- private HashMap<String, StyleSet> mStyleSetCache;
-
- /*package*/ static final String DEFAULT_MIMETYPE = "default-icon";
- private static final String ICON_SET_META_DATA = "com.android.contacts.iconset";
- private static final String TAG_ICON_SET = "icon-set";
- private static final String TAG_ICON = "icon";
- private static final String TAG_ICON_DEFAULT = "icon-default";
- private static final String KEY_JOIN_CHAR = "|";
-
- private StyleManager(Context context) {
- mIconCache = new WeakHashMap<String, Bitmap>();
- mStyleSetCache = new HashMap<String, StyleSet>();
- registerIntentReceivers(context);
- }
-
- /**
- * Returns an instance of StyleManager. This method enforces that only a single instance of this
- * class exists at any one time in a process.
- *
- * @param context A context object
- * @return StyleManager object
- */
- public static StyleManager getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new StyleManager(context);
- }
- return sInstance;
- }
-
- private void registerIntentReceivers(Context context) {
- IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addDataScheme("package");
-
- // We use getApplicationContext() so that the broadcast reciever can stay registered for
- // the length of the application lifetime (instead of the calling activity's lifetime).
- // This is so that we can notified of package changes, and purge the cache accordingly,
- // but not be woken up if the application process isn't already running, since we will
- // have no cache to clear at that point.
- context.getApplicationContext().registerReceiver(this, filter);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final String packageName = intent.getData().getSchemeSpecificPart();
-
- if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
- || Intent.ACTION_PACKAGE_ADDED.equals(action)
- || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
- onPackageChange(packageName);
- }
- }
-
- public void onPackageChange(String packageName) {
- Iterator<String> itr;
-
- // Remove cached icons for this package
- for (itr = mIconCache.keySet().iterator(); itr.hasNext(); ) {
- if (itr.next().startsWith(packageName + KEY_JOIN_CHAR)) {
- itr.remove();
- }
- }
-
- // Remove the cached style set for this package
- mStyleSetCache.remove(packageName);
- }
-
- /**
- * Get the default icon for a given package. If no icon is specified for that package
- * null is returned.
- *
- * @param packageName
- * @return Bitmap holding the default icon.
- */
- public Bitmap getDefaultIcon(Context context, String packageName) {
- return getMimetypeIcon(context, packageName, DEFAULT_MIMETYPE);
- }
-
- /**
- * Get the icon associated with a mimetype for a given package. If no icon is specified for that
- * package null is returned.
- *
- * @param packageName
- * @return Bitmap holding the default icon.
- */
- public Bitmap getMimetypeIcon(Context context, String packageName, String mimetype) {
- String key = getKey(packageName, mimetype);
-
- synchronized(mIconCache) {
- if (!mIconCache.containsKey(key)) {
- // Cache miss
-
- // loadIcon() may return null, which is fine since, if no icon was found we want to
- // store a null value so we know not to look next time.
- mIconCache.put(key, loadIcon(context, packageName, mimetype));
- }
- return mIconCache.get(key);
- }
- }
-
- private Bitmap loadIcon(Context context, String packageName, String mimetype) {
- StyleSet ss = null;
-
- synchronized(mStyleSetCache) {
- if (!mStyleSetCache.containsKey(packageName)) {
- // Cache miss
- try {
- StyleSet inflated = inflateStyleSet(context, packageName);
- mStyleSetCache.put(packageName, inflated);
- } catch (InflateException e) {
- // If inflation failed keep a null entry so we know not to try again.
- Log.w(TAG, "Inflation failed: " + e);
- mStyleSetCache.put(packageName, null);
- }
- }
- }
-
- ss = mStyleSetCache.get(packageName);
- if (ss == null) {
- return null;
- }
-
- int iconRes;
- if ((iconRes = ss.getIconRes(mimetype)) == -1) {
- return null;
- }
-
- return BitmapFactory.decodeResource(context.getResources(),
- iconRes, null);
- }
-
- private StyleSet inflateStyleSet(Context context, String packageName) throws InflateException {
- final PackageManager pm = context.getPackageManager();
- final ApplicationInfo ai;
-
- try {
- ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
- } catch (NameNotFoundException e) {
- return null;
- }
-
- XmlPullParser parser = ai.loadXmlMetaData(pm, ICON_SET_META_DATA);
- final AttributeSet attrs = Xml.asAttributeSet(parser);
-
- if (parser == null) {
- return null;
- }
-
- try {
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- // Drain comments and whitespace
- }
-
- if (type != XmlPullParser.START_TAG) {
- throw new InflateException("No start tag found");
- }
-
- if (!TAG_ICON_SET.equals(parser.getName())) {
- throw new InflateException("Top level element must be StyleSet");
- }
-
- // Parse all children actions
- StyleSet styleSet = new StyleSet();
- final int depth = parser.getDepth();
- while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
- && type != XmlPullParser.END_DOCUMENT) {
- if (type == XmlPullParser.END_TAG) {
- continue;
- }
-
- TypedArray a;
-
- String mimetype;
- if (TAG_ICON.equals(parser.getName())) {
- a = context.obtainStyledAttributes(attrs, android.R.styleable.Icon);
- mimetype = a.getString(com.android.internal.R.styleable.Icon_mimeType);
- if (mimetype != null) {
- styleSet.addIcon(mimetype,
- a.getResourceId(com.android.internal.R.styleable.Icon_icon, -1));
- }
- } else if (TAG_ICON_DEFAULT.equals(parser.getName())) {
- a = context.obtainStyledAttributes(attrs, android.R.styleable.IconDefault);
- styleSet.addIcon(DEFAULT_MIMETYPE,
- a.getResourceId(
- com.android.internal.R.styleable.IconDefault_icon, -1));
- } else {
- throw new InflateException("Expected " + TAG_ICON + " or "
- + TAG_ICON_DEFAULT + " tag");
- }
- }
- return styleSet;
-
- } catch (XmlPullParserException e) {
- throw new InflateException("Problem reading XML", e);
- } catch (IOException e) {
- throw new InflateException("Problem reading XML", e);
- }
- }
-
- private String getKey(String packageName, String mimetype) {
- return packageName + KEY_JOIN_CHAR + mimetype;
- }
-
- public static class InflateException extends Exception {
- public InflateException(String message) {
- super(message);
- }
-
- public InflateException(String message, Throwable throwable) {
- super(message, throwable);
- }
- }
-
- private static class StyleSet {
- private HashMap<String, Integer> mMimetypeIconResMap;
-
- public StyleSet() {
- mMimetypeIconResMap = new HashMap<String, Integer>();
- }
-
- public int getIconRes(String mimetype) {
- if (!mMimetypeIconResMap.containsKey(mimetype)) {
- return -1;
- }
- return mMimetypeIconResMap.get(mimetype);
- }
-
- public void addIcon(String mimetype, int res) {
- if (mimetype == null) {
- return;
- }
- mMimetypeIconResMap.put(mimetype, res);
- }
- }
-
- //-------------------------------------------//
- //-- Methods strictly for testing purposes --//
- //-------------------------------------------//
-
- /*package*/ int getIconCacheSize() {
- return mIconCache.size();
- }
-
- /*package*/ int getStyleSetCacheSize() {
- return mStyleSetCache.size();
- }
-
- /*package*/ boolean isStyleSetCacheHit(String packageName) {
- return mStyleSetCache.containsKey(packageName);
- }
-
- /*package*/ boolean isIconCacheHit(String packageName, String mimetype) {
- return mIconCache.containsKey(getKey(packageName, mimetype));
- }
-
- //-------------------------------------------//
-}
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index be78bea..76c7b7e 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -302,7 +302,8 @@
// TODO: ensure inflation on background task so we don't block UI thread here
final ContactsSource source = sources.getInflatedSource(accountType,
ContactsSource.LEVEL_SUMMARY);
- addTab(rawContactId, ContactsUtils.createTabIndicatorView(mTabWidget.getTabParent(), source));
+ addTab(rawContactId, ContactsUtils.createTabIndicatorView(mTabWidget.getTabParent(),
+ source));
}
}
@@ -846,12 +847,11 @@
for (Entity entity: mEntities) {
final ContentValues entValues = entity.getEntityValues();
final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
- // TODO: entry.contactId should be renamed to entry.rawContactId
- long contactId = entValues.getAsLong(RawContacts._ID);
+ final long rawContactId = entValues.getAsLong(RawContacts._ID);
// This performs the tab filtering
if (mSelectedRawContactId != null
- && mSelectedRawContactId != contactId
+ && mSelectedRawContactId != rawContactId
&& mSelectedRawContactId != ALL_CONTACTS_ID) {
continue;
}
@@ -859,26 +859,19 @@
for (NamedContentValues subValue : entity.getSubValues()) {
ViewEntry entry = new ViewEntry();
- ContentValues entryValues = subValue.values;
+ final ContentValues entryValues = subValue.values;
+ entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
+
final String mimetype = entryValues.getAsString(Data.MIMETYPE);
- if (mimetype == null || accountType == null) {
- continue;
- }
+ if (mimetype == null) continue;
- ContactsSource contactsSource = sources.getInflatedSource(accountType,
+ final DataKind kind = sources.getKindOrFallback(accountType, mimetype, this,
ContactsSource.LEVEL_MIMETYPES);
- if (contactsSource == null) {
- continue;
- }
-
- DataKind kind = contactsSource.getKindForMimetype(mimetype);
- if (kind == null) {
- continue;
- }
+ if (kind == null) continue;
final long id = entryValues.getAsLong(Data._ID);
final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
- entry.contactId = contactId;
+ entry.contactId = rawContactId;
entry.id = id;
entry.uri = uri;
entry.mimetype = mimetype;
@@ -888,12 +881,12 @@
entry.type = entryValues.getAsInteger(kind.typeColumn);
}
if (kind.iconRes > 0) {
+ entry.resPackageName = kind.resPackageName;
entry.actionIcon = kind.iconRes;
}
// Don't crash if the data is bogus
if (TextUtils.isEmpty(entry.data)) {
- Log.w(TAG, "empty data for contact method " + id);
continue;
}
@@ -938,10 +931,6 @@
// Build email entries
entry.intent = new Intent(Intent.ACTION_SENDTO,
Uri.fromParts("mailto", entry.data, null));
- // Temporary hack until we get real label resources for exchange.
- if (TextUtils.isEmpty(entry.label)) {
- entry.label = getString(R.string.email).toLowerCase();
- }
entry.isPrimary = isSuperPrimary;
mEmailEntries.add(entry);
} else if (CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE.
@@ -956,7 +945,7 @@
String host = null;
if (TextUtils.isEmpty(entry.label)) {
- entry.label = getString(R.string.chat_other).toLowerCase();
+ entry.label = getString(R.string.chat).toLowerCase();
}
if (protocolObj instanceof Number) {
@@ -995,9 +984,12 @@
entry.intent = null;
entry.maxLines = 10;
mOtherEntries.add(entry);
+ } else {
+ // Handle showing custom
+ entry.intent = new Intent(Intent.ACTION_VIEW, uri);
+ mOtherEntries.add(entry);
}
-
// TODO(emillar) Add group entries
// // Build the group entries
// final Uri groupsUri = Uri.withAppendedPath(mUri, GroupMembership.CONTENT_DIRECTORY);
@@ -1069,6 +1061,7 @@
* A basic structure with the data for a contact entry in the list.
*/
static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
+ public String resPackageName = null;
public int actionIcon = -1;
public boolean isPrimary = false;
public int presenceIcon = -1;
@@ -1225,7 +1218,15 @@
// Set the action icon
ImageView action = views.actionIcon;
if (entry.actionIcon != -1) {
- action.setImageDrawable(resources.getDrawable(entry.actionIcon));
+ Drawable actionIcon;
+ if (entry.resPackageName != null) {
+ // Load external resources through PackageManager
+ actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
+ entry.actionIcon, null);
+ } else {
+ actionIcon = resources.getDrawable(entry.actionIcon);
+ }
+ action.setImageDrawable(actionIcon);
action.setVisibility(View.VISIBLE);
} else {
// Things should still line up as if there was an icon, so make it invisible
diff --git a/src/com/android/contacts/model/ContactsSource.java b/src/com/android/contacts/model/ContactsSource.java
index 1e797d4..f0c21e3 100644
--- a/src/com/android/contacts/model/ContactsSource.java
+++ b/src/com/android/contacts/model/ContactsSource.java
@@ -17,17 +17,14 @@
package com.android.contacts.model;
import com.google.android.collect.Lists;
-
-import org.xmlpull.v1.XmlPullParser;
+import com.google.android.collect.Maps;
import android.accounts.Account;
import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.XmlResourceParser;
import android.database.Cursor;
+import android.graphics.drawable.Drawable;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
@@ -38,41 +35,9 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
-/*
-
-<!-- example of what SourceConstraints would look like in XML -->
-<!-- NOTE: may not directly match the current structure version -->
-
-<DataKind
- mimeType="vnd.android.cursor.item/email"
- title="@string/title_postal"
- icon="@drawable/icon_postal"
- weight="12"
- editable="true">
-
- <!-- these are defined using string-builder-ish -->
- <ActionHeader></ActionHeader>
- <ActionBody socialSummary="true" /> <!-- can pull together various columns -->
-
- <!-- ordering handles precedence the "insert/add" case -->
- <!-- assume uniform type when missing "column", use title in place -->
- <EditTypes column="data5" overallMax="-1">
- <EditType rawValue="0" label="@string/type_home" specificMax="-1" />
- <EditType rawValue="1" label="@string/type_work" specificMax="-1" secondary="true" />
- <EditType rawValue="4" label="@string/type_custom" customColumn="data6" specificMax="-1" secondary="true" />
- </EditTypes>
-
- <!-- when single edit field, simplifies edit case -->
- <EditField column="data1" title="@string/field_family_name" android:inputType="textCapWords|textPhonetic" />
- <EditField column="data2" title="@string/field_given_name" android:minLines="2" />
- <EditField column="data3" title="@string/field_suffix" />
-
-</DataKind>
-
-*/
-
/**
* Internal structure that represents constraints and styles for a specific data
* source, such as the various data types they support, including details on how
@@ -80,7 +45,7 @@
* <p>
* In the future this may be inflated from XML defined by a data source.
*/
-public class ContactsSource {
+public abstract class ContactsSource {
/**
* The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to.
*/
@@ -91,6 +56,7 @@
* {@link Account} or for matching against {@link Data#RES_PACKAGE}.
*/
public String resPackageName;
+ public String summaryResPackageName;
public int titleRes;
public int iconRes;
@@ -102,14 +68,17 @@
*/
private ArrayList<DataKind> mKinds = Lists.newArrayList();
- private static final String ACTION_SYNC_ADAPTER = "android.content.SyncAdapter";
- private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE";
+ /**
+ * Lookup map of {@link #mKinds} on {@link DataKind#mimeType}.
+ */
+ private HashMap<String, DataKind> mMimeKinds = Maps.newHashMap();
+ public static final int LEVEL_NONE = 0;
public static final int LEVEL_SUMMARY = 1;
public static final int LEVEL_MIMETYPES = 2;
public static final int LEVEL_CONSTRAINTS = 3;
- private int mInflatedLevel = -1;
+ private int mInflatedLevel = LEVEL_NONE;
public synchronized boolean isInflated(int inflateLevel) {
return mInflatedLevel >= inflateLevel;
@@ -121,62 +90,54 @@
}
/**
- * Ensure that the constraint rules behind this {@link ContactsSource} have
- * been inflated. Because this may involve parsing meta-data from
- * {@link PackageManager}, it shouldn't be called from a UI thread.
+ * Ensure that this {@link ContactsSource} has been inflated to the
+ * requested level.
*/
public synchronized void ensureInflated(Context context, int inflateLevel) {
- if (isInflated(inflateLevel)) return;
- // TODO: handle inflating at multiple levels of parsing
- mInflatedLevel = inflateLevel;
- mKinds.clear();
-
- // Handle some well-known sources with hard-coded constraints
- // TODO: move these into adapter-specific XML once schema finalized
- if (HardCodedSources.ACCOUNT_TYPE_FALLBACK.equals(accountType)) {
- HardCodedSources.buildFallback(context, this);
- return;
- } else if (HardCodedSources.ACCOUNT_TYPE_GOOGLE.equals(accountType)) {
- HardCodedSources.buildGoogle(context, this);
- return;
- } else if(HardCodedSources.ACCOUNT_TYPE_EXCHANGE.equals(accountType)) {
- HardCodedSources.buildExchange(context, this);
- return;
- } else if(HardCodedSources.ACCOUNT_TYPE_FACEBOOK.equals(accountType)) {
- HardCodedSources.buildFacebook(context, this);
- return;
- }
-
- // Handle unknown sources by searching their package
- final PackageManager pm = context.getPackageManager();
- final Intent syncAdapter = new Intent(ACTION_SYNC_ADAPTER);
- final List<ResolveInfo> matches = pm.queryIntentServices(syncAdapter,
- PackageManager.GET_META_DATA);
- for (ResolveInfo info : matches) {
- final XmlResourceParser parser = info.activityInfo.loadXmlMetaData(pm,
- METADATA_CONTACTS);
- inflate(parser);
+ if (!isInflated(inflateLevel)) {
+ inflate(context, inflateLevel);
}
}
/**
- * Inflate this {@link ContactsSource} from the given parser. This may only
- * load details matching the publicly-defined schema.
+ * Perform the actual inflation to the requested level. Called by
+ * {@link #ensureInflated(Context, int)} when inflation is needed.
*/
- protected void inflate(XmlPullParser parser) {
- // TODO: implement basic functionality for third-party integration
- throw new UnsupportedOperationException("Custom constraint parser not implemented");
+ protected abstract void inflate(Context context, int inflateLevel);
+
+ /**
+ * Invalidate any cache for this {@link ContactsSource}, removing all
+ * inflated data. Calling {@link #ensureInflated(Context, int)} will
+ * populate again from scratch.
+ */
+ public synchronized void invalidateCache() {
+ this.mKinds.clear();
+ this.mMimeKinds.clear();
+ setInflatedLevel(LEVEL_NONE);
}
public CharSequence getDisplayLabel(Context context) {
- if (this.titleRes > 0) {
+ if (this.titleRes != -1 && this.summaryResPackageName != null) {
final PackageManager pm = context.getPackageManager();
- return pm.getText(this.resPackageName, this.titleRes, null);
+ return pm.getText(this.summaryResPackageName, this.titleRes, null);
+ } else if (this.titleRes != -1) {
+ return context.getText(this.titleRes);
} else {
return this.accountType;
}
}
+ public Drawable getDisplayIcon(Context context) {
+ if (this.titleRes != -1 && this.summaryResPackageName != null) {
+ final PackageManager pm = context.getPackageManager();
+ return pm.getDrawable(this.summaryResPackageName, this.iconRes, null);
+ } else if (this.titleRes != -1) {
+ return context.getResources().getDrawable(this.iconRes);
+ } else {
+ return null;
+ }
+ }
+
/**
* {@link Comparator} to sort by {@link DataKind#weight}.
*/
@@ -197,20 +158,22 @@
}
/**
- * Find the {@link DataKind} for a specifc MIME-type, if it's handled by
- * this data source.
+ * Find the {@link DataKind} for a specific MIME-type, if it's handled by
+ * this data source. If you may need a fallback {@link DataKind}, use
+ * {@link Sources#getKindOrFallback(String, String, Context, int)}.
*/
public DataKind getKindForMimetype(String mimeType) {
- for (DataKind kind : mKinds) {
- if (mimeType.equals(kind.mimeType)) {
- return kind;
- }
- }
- return null;
+ return this.mMimeKinds.get(mimeType);
}
- public void add(DataKind kind) {
+ /**
+ * Add given {@link DataKind} to list of those provided by this source.
+ */
+ public DataKind addKind(DataKind kind) {
+ kind.resPackageName = this.resPackageName;
this.mKinds.add(kind);
+ this.mMimeKinds.put(kind.mimeType, kind);
+ return kind;
}
/**
@@ -220,6 +183,7 @@
* labels and editable {@link EditField}.
*/
public static class DataKind {
+ public String resPackageName;
public String mimeType;
public int titleRes;
public int iconRes;
@@ -231,6 +195,7 @@
public StringInflater actionHeader;
public StringInflater actionAltHeader;
public StringInflater actionBody;
+ public StringInflater actionFooter;
public boolean actionBodySocial;
public boolean actionBodyCombine;
@@ -242,6 +207,9 @@
public ContentValues defaultValues;
+ public DataKind() {
+ }
+
public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable) {
this.mimeType = mimeType;
this.titleRes = titleRes;
@@ -261,8 +229,8 @@
public static class EditType {
public int rawValue;
public int labelRes;
- public int actionRes;
- public int actionAltRes;
+// public int actionRes;
+// public int actionAltRes;
public boolean secondary;
public int specificMax;
public String customColumn;
@@ -273,16 +241,6 @@
this.specificMax = -1;
}
- public EditType(int rawValue, int labelRes, int actionRes) {
- this(rawValue, labelRes);
- this.actionRes = actionRes;
- }
-
- public EditType(int rawValue, int labelRes, int actionRes, int actionAltRes) {
- this(rawValue, labelRes, actionRes);
- this.actionAltRes = actionAltRes;
- }
-
public EditType setSecondary(boolean secondary) {
this.secondary = secondary;
return this;
@@ -335,9 +293,9 @@
this.inputType = inputType;
}
- public EditField(String column, int titleRes, int inputType, boolean optional) {
- this(column, titleRes, inputType);
+ public EditField setOptional(boolean optional) {
this.optional = optional;
+ return this;
}
}
diff --git a/src/com/android/contacts/model/ExchangeSource.java b/src/com/android/contacts/model/ExchangeSource.java
new file mode 100644
index 0000000..c017037
--- /dev/null
+++ b/src/com/android/contacts/model/ExchangeSource.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2009 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.model;
+
+import com.android.contacts.R;
+import com.google.android.collect.Lists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+public class ExchangeSource extends FallbackSource {
+
+ public static final String ACCOUNT_TYPE = "com.android.exchange";
+
+ public ExchangeSource(String resPackageName) {
+ this.accountType = ACCOUNT_TYPE;
+ this.resPackageName = null;
+ this.summaryResPackageName = resPackageName;
+ }
+
+ @Override
+ protected void inflate(Context context, int inflateLevel) {
+
+ inflateStructuredName(inflateLevel);
+ inflateNickname(inflateLevel);
+ inflatePhone(inflateLevel);
+ inflateEmail(inflateLevel);
+ inflateStructuredPostal(inflateLevel);
+ inflateIm(inflateLevel);
+ inflateOrganization(inflateLevel);
+ inflatePhoto(inflateLevel);
+ inflateNote(inflateLevel);
+ inflateWebsite(inflateLevel);
+
+ setInflatedLevel(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateStructuredName(int inflateLevel) {
+ final DataKind kind = super.inflateStructuredName(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+ FLAGS_PERSON_NAME));
+ kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
+ FLAGS_PERSON_NAME));
+ kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+ R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+ R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateNickname(int inflateLevel) {
+ final DataKind kind = super.inflateNickname(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
+ FLAGS_PERSON_NAME));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflatePhone(int inflateLevel) {
+ final DataKind kind = super.inflatePhone(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPhoneType(Phone.TYPE_HOME).setSpecificMax(2));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE).setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK).setSpecificMax(2));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true)
+ .setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true)
+ .setSpecificMax(1));
+ kind.typeList
+ .add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true).setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true).setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true)
+ .setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true).setSpecificMax(1));
+ kind.typeList
+ .add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true).setSpecificMax(1));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true)
+ .setSpecificMax(1).setCustomColumn(Phone.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateEmail(int inflateLevel) {
+ final DataKind kind = super.inflateEmail(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 3;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateStructuredPostal(int inflateLevel) {
+ final DataKind kind = super.inflateStructuredPostal(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = StructuredPostal.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK).setSpecificMax(1));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME).setSpecificMax(1));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER).setSpecificMax(1));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
+ FLAGS_POSTAL));
+ kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
+ FLAGS_POSTAL));
+ kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
+ FLAGS_POSTAL));
+ kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
+ FLAGS_POSTAL));
+ kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
+ FLAGS_POSTAL).setOptional(true));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateIm(int inflateLevel) {
+ final DataKind kind = super.inflateIm(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 3;
+
+ // NOTE: even though a traditional "type" exists, for editing
+ // purposes we're using the protocol to pick labels
+
+ kind.defaultValues = new ContentValues();
+ kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
+
+ kind.typeColumn = Im.PROTOCOL;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
+ kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
+ kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
+ kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
+ kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
+ kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
+ kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
+ kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
+ kind.typeList.add(buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(
+ Im.CUSTOM_PROTOCOL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateOrganization(int inflateLevel) {
+ final DataKind kind = super.inflateOrganization(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Organization.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildOrgType(Organization.TYPE_WORK).setSpecificMax(1));
+ kind.typeList.add(buildOrgType(Organization.TYPE_OTHER).setSpecificMax(1));
+ kind.typeList.add(buildOrgType(Organization.TYPE_CUSTOM).setSecondary(true)
+ .setSpecificMax(1));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
+ FLAGS_GENERIC_NAME));
+ kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
+ FLAGS_GENERIC_NAME));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflatePhoto(int inflateLevel) {
+ final DataKind kind = super.inflatePhoto(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateNote(int inflateLevel) {
+ final DataKind kind = super.inflateNote(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateWebsite(int inflateLevel) {
+ final DataKind kind = super.inflateWebsite(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeOverallMax = 1;
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
+ }
+
+ return kind;
+ }
+}
diff --git a/src/com/android/contacts/model/ExternalSource.java b/src/com/android/contacts/model/ExternalSource.java
new file mode 100644
index 0000000..f22aba4
--- /dev/null
+++ b/src/com/android/contacts/model/ExternalSource.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2009 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.model;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.provider.ContactsContract.Data;
+import android.provider.SocialContract.Activities;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.util.List;
+
+/*
+
+<!-- example of what SourceConstraints would look like in XML -->
+<!-- NOTE: may not directly match the current structure version -->
+
+<DataKind
+ mimeType="vnd.android.cursor.item/email"
+ title="@string/title_postal"
+ icon="@drawable/icon_postal"
+ weight="12"
+ editable="true">
+
+ <!-- these are defined using string-builder-ish -->
+ <ActionHeader></ActionHeader>
+ <ActionBody socialSummary="true" /> <!-- can pull together various columns -->
+
+ <!-- ordering handles precedence the "insert/add" case -->
+ <!-- assume uniform type when missing "column", use title in place -->
+ <EditTypes column="data5" overallMax="-1">
+ <EditType rawValue="0" label="@string/type_home" specificMax="-1" />
+ <EditType rawValue="1" label="@string/type_work" specificMax="-1" secondary="true" />
+ <EditType rawValue="4" label="@string/type_custom" customColumn="data6" specificMax="-1" secondary="true" />
+ </EditTypes>
+
+ <!-- when single edit field, simplifies edit case -->
+ <EditField column="data1" title="@string/field_family_name" android:inputType="textCapWords|textPhonetic" />
+ <EditField column="data2" title="@string/field_given_name" android:minLines="2" />
+ <EditField column="data3" title="@string/field_suffix" />
+
+</DataKind>
+
+*/
+
+/**
+ * Internal structure that represents constraints and styles for a specific data
+ * source, such as the various data types they support, including details on how
+ * those types should be rendered and edited.
+ * <p>
+ * In the future this may be inflated from XML defined by a data source.
+ */
+public class ExternalSource extends ContactsSource {
+ private static final String ACTION_SYNC_ADAPTER = "android.content.SyncAdapter";
+ private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE";
+
+ private interface InflateTags {
+ final String CONTACTS_SOURCE = "ContactsSource";
+ final String CONTACTS_DATA_KIND = "ContactsDataKind";
+ }
+
+ public ExternalSource(String resPackageName) {
+ this.resPackageName = resPackageName;
+ this.summaryResPackageName = resPackageName;
+ }
+
+ /**
+ * Ensure that the constraint rules behind this {@link ContactsSource} have
+ * been inflated. Because this may involve parsing meta-data from
+ * {@link PackageManager}, it shouldn't be called from a UI thread.
+ */
+ @Override
+ public void inflate(Context context, int inflateLevel) {
+ // Handle unknown sources by searching their package
+ final PackageManager pm = context.getPackageManager();
+ final Intent syncAdapter = new Intent(ACTION_SYNC_ADAPTER);
+ final List<ResolveInfo> matches = pm.queryIntentServices(syncAdapter,
+ PackageManager.GET_META_DATA);
+ for (ResolveInfo info : matches) {
+ final XmlResourceParser parser = info.serviceInfo.loadXmlMetaData(pm,
+ METADATA_CONTACTS);
+ if (parser == null) continue;
+ inflate(context, parser);
+ }
+ }
+
+ /**
+ * Inflate this {@link ContactsSource} from the given parser. This may only
+ * load details matching the publicly-defined schema.
+ */
+ protected void inflate(Context context, XmlPullParser parser) {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ try {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Drain comments and whitespace
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("No start tag found");
+ }
+
+ if (!InflateTags.CONTACTS_SOURCE.equals(parser.getName())) {
+ throw new IllegalStateException("Top level element must be "
+ + InflateTags.CONTACTS_SOURCE);
+ }
+
+ // Parse all children kinds
+ final int depth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ && type != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.END_TAG
+ || !InflateTags.CONTACTS_DATA_KIND.equals(parser.getName())) {
+ continue;
+ }
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ android.R.styleable.ContactsDataKind);
+ final DataKind kind = new DataKind();
+
+ kind.mimeType = a
+ .getString(com.android.internal.R.styleable.ContactsDataKind_mimeType);
+ kind.iconRes = a.getResourceId(
+ com.android.internal.R.styleable.ContactsDataKind_icon, -1);
+
+ final String summaryColumn = a
+ .getString(com.android.internal.R.styleable.ContactsDataKind_summaryColumn);
+ if (summaryColumn != null) {
+ // Inflate a specific column as summary when requested
+ kind.actionHeader = new FallbackSource.SimpleInflater(summaryColumn);
+ }
+
+ final String detailColumn = a
+ .getString(com.android.internal.R.styleable.ContactsDataKind_detailColumn);
+ final boolean detailSocialSummary = a.getBoolean(
+ com.android.internal.R.styleable.ContactsDataKind_detailSocialSummary,
+ false);
+ if (detailSocialSummary) {
+ // Inflate social summary when requested
+ kind.actionBody = new SocialInflater(false);
+ kind.actionFooter = new SocialInflater(true);
+ } else {
+ // Otherwise inflate specific column as summary
+ kind.actionBody = new FallbackSource.SimpleInflater(detailColumn);
+ }
+
+ addKind(kind);
+ }
+ } catch (XmlPullParserException e) {
+ throw new IllegalStateException("Problem reading XML", e);
+ } catch (IOException e) {
+ throw new IllegalStateException("Problem reading XML", e);
+ }
+ }
+
+ /**
+ * Temporary cache to hold recent social data.
+ */
+ private static class SocialCache {
+ private static Status sLastStatus = null;
+
+ public static class Status {
+ public long rawContactId;
+ public CharSequence title;
+ public long published;
+ }
+
+ public static synchronized Status getLatestStatus(Context context, long rawContactId) {
+ if (sLastStatus == null || sLastStatus.rawContactId != rawContactId) {
+ // Cache missing, or miss, so query directly
+ sLastStatus = queryLatestStatus(context, rawContactId);
+ }
+ return sLastStatus;
+ }
+
+ private static Status queryLatestStatus(Context context, long rawContactId) {
+ // Find latest social update by this person, filtering to show only
+ // original content and avoid replies.
+ final ContentResolver resolver = context.getContentResolver();
+ final Cursor cursor = resolver.query(Activities.CONTENT_URI, new String[] {
+ Activities.TITLE, Activities.PUBLISHED
+ }, Activities.AUTHOR_CONTACT_ID + "=" + rawContactId + " AND "
+ + Activities.IN_REPLY_TO + " IS NULL", null, Activities.PUBLISHED + " DESC");
+
+ final Status status = new Status();
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ status.title = cursor.getString(0);
+ status.published = cursor.getLong(1);
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return status;
+ }
+ }
+
+ /**
+ * Inflater that will return the latest {@link Activities#TITLE} and
+ * {@link Activities#PUBLISHED} for the given {@link Data#RAW_CONTACT_ID}.
+ */
+ protected static class SocialInflater implements StringInflater {
+ private final boolean mPublishedMode;
+
+ public SocialInflater(boolean publishedMode) {
+ mPublishedMode = publishedMode;
+ }
+
+ protected CharSequence inflatePublished(long published) {
+ return DateUtils.getRelativeTimeSpanString(published, System.currentTimeMillis(),
+ DateUtils.MINUTE_IN_MILLIS);
+ }
+
+ /** {@inheritDoc} */
+ public CharSequence inflateUsing(Context context, Cursor cursor) {
+ final Long rawContactId = cursor.getLong(cursor.getColumnIndex(Data.RAW_CONTACT_ID));
+ if (rawContactId == null) return null;
+
+ final SocialCache.Status status = SocialCache.getLatestStatus(context, rawContactId);
+ return mPublishedMode ? inflatePublished(status.published) : status.title;
+ }
+
+ /** {@inheritDoc} */
+ public CharSequence inflateUsing(Context context, ContentValues values) {
+ final Long rawContactId = values.getAsLong(Data.RAW_CONTACT_ID);
+ if (rawContactId == null) return null;
+
+ final SocialCache.Status status = SocialCache.getLatestStatus(context, rawContactId);
+ return mPublishedMode ? inflatePublished(status.published) : status.title;
+ }
+ }
+}
diff --git a/src/com/android/contacts/model/FallbackSource.java b/src/com/android/contacts/model/FallbackSource.java
new file mode 100644
index 0000000..85aedd7
--- /dev/null
+++ b/src/com/android/contacts/model/FallbackSource.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2009 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.model;
+
+import com.android.contacts.R;
+import com.google.android.collect.Lists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+
+public class FallbackSource extends ContactsSource {
+ protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
+ protected static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+ protected static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME;
+ protected static final int FLAGS_PHONETIC = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_PHONETIC;
+ protected static final int FLAGS_GENERIC_NAME = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+ protected static final int FLAGS_NOTE = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ protected static final int FLAGS_WEBSITE = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_URI;
+ protected static final int FLAGS_POSTAL = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
+ | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+
+ public FallbackSource() {
+ this.accountType = null;
+ this.titleRes = R.string.account_phone;
+ this.iconRes = R.drawable.ic_launcher_contacts;
+ }
+
+ @Override
+ protected void inflate(Context context, int inflateLevel) {
+
+ inflateStructuredName(inflateLevel);
+ inflateNickname(inflateLevel);
+ inflatePhone(inflateLevel);
+ inflateEmail(inflateLevel);
+ inflateStructuredPostal(inflateLevel);
+ inflateIm(inflateLevel);
+ inflateOrganization(inflateLevel);
+ inflatePhoto(inflateLevel);
+ inflateNote(inflateLevel);
+ inflateWebsite(inflateLevel);
+
+ setInflatedLevel(inflateLevel);
+
+ }
+
+ protected EditType buildPhoneType(int type) {
+ return new EditType(type, Phone.getTypeLabelResource(type));
+ }
+
+ protected EditType buildEmailType(int type) {
+ return new EditType(type, Email.getTypeLabelResource(type));
+ }
+
+ protected EditType buildPostalType(int type) {
+ return new EditType(type, StructuredPostal.getTypeLabelResource(type));
+ }
+
+ protected EditType buildImType(int type) {
+ return new EditType(type, Im.getProtocolLabelResource(type));
+ }
+
+ protected EditType buildOrgType(int type) {
+ return new EditType(type, Organization.getTypeLabelResource(type));
+ }
+
+ protected DataKind inflateStructuredName(int inflateLevel) {
+ DataKind kind = getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
+ R.string.nameLabelsGroup, -1, -1, true));
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+ FLAGS_PERSON_NAME));
+ kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
+ FLAGS_PERSON_NAME));
+ kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
+ FLAGS_PERSON_NAME).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+ R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
+ R.string.name_phonetic_middle, FLAGS_PHONETIC).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+ R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateNickname(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Nickname.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
+ R.string.nicknameLabelsGroup, -1, 115, true));
+ kind.secondary = true;
+ kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
+ kind.actionBody = new SimpleInflater(Nickname.NAME);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
+ FLAGS_PERSON_NAME));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflatePhone(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
+ android.R.drawable.sym_action_call, 10, true));
+ kind.iconAltRes = R.drawable.sym_action_sms;
+ kind.actionHeader = new PhoneActionInflater();
+ kind.actionAltHeader = new PhoneActionAltInflater();
+ kind.actionBody = new SimpleInflater(Phone.NUMBER);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+ Phone.LABEL));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CALLBACK).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CAR).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_COMPANY_MAIN).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_ISDN).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MAIN).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER_FAX).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_TELEX).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_TTY_TDD).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_MOBILE).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK_PAGER).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true)
+ .setCustomColumn(Phone.LABEL));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateEmail(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Email.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE,
+ R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true));
+ kind.actionHeader = new EmailActionInflater();
+ kind.actionBody = new SimpleInflater(Email.DATA);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Email.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildEmailType(Email.TYPE_HOME));
+ kind.typeList.add(buildEmailType(Email.TYPE_WORK));
+ kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
+ kind.typeList.add(buildEmailType(Email.TYPE_MOBILE));
+ kind.typeList.add(buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+ Email.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateStructuredPostal(int inflateLevel) {
+ DataKind kind = getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
+ R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true));
+ kind.actionHeader = new PostalActionInflater();
+ kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = StructuredPostal.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER));
+ kind.typeList.add(buildPostalType(StructuredPostal.TYPE_CUSTOM).setSecondary(true)
+ .setCustomColumn(StructuredPostal.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
+ FLAGS_POSTAL));
+ kind.fieldList.add(new EditField(StructuredPostal.POBOX, R.string.postal_pobox,
+ FLAGS_POSTAL).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
+ R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
+ kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
+ FLAGS_POSTAL));
+ kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
+ FLAGS_POSTAL));
+ kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
+ FLAGS_POSTAL));
+ kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
+ FLAGS_POSTAL).setOptional(true));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateIm(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Im.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
+ android.R.drawable.sym_action_chat, 20, true));
+ kind.actionHeader = new ImActionInflater();
+ kind.actionBody = new SimpleInflater(Im.DATA);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ // NOTE: even though a traditional "type" exists, for editing
+ // purposes we're using the protocol to pick labels
+
+ kind.defaultValues = new ContentValues();
+ kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
+
+ kind.typeColumn = Im.PROTOCOL;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildImType(Im.PROTOCOL_AIM));
+ kind.typeList.add(buildImType(Im.PROTOCOL_MSN));
+ kind.typeList.add(buildImType(Im.PROTOCOL_YAHOO));
+ kind.typeList.add(buildImType(Im.PROTOCOL_SKYPE));
+ kind.typeList.add(buildImType(Im.PROTOCOL_QQ));
+ kind.typeList.add(buildImType(Im.PROTOCOL_GOOGLE_TALK));
+ kind.typeList.add(buildImType(Im.PROTOCOL_ICQ));
+ kind.typeList.add(buildImType(Im.PROTOCOL_JABBER));
+ kind.typeList.add(buildImType(Im.PROTOCOL_CUSTOM).setSecondary(true).setCustomColumn(
+ Im.CUSTOM_PROTOCOL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateOrganization(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
+ R.string.organizationLabelsGroup, R.drawable.sym_action_organization, 30, true));
+ kind.actionHeader = new SimpleInflater(Organization.COMPANY);
+ kind.actionBody = new SimpleInflater(Organization.TITLE);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Organization.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildOrgType(Organization.TYPE_WORK));
+ kind.typeList.add(buildOrgType(Organization.TYPE_OTHER));
+ kind.typeList.add(buildOrgType(Organization.TYPE_CUSTOM).setSecondary(true)
+ .setCustomColumn(Organization.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
+ FLAGS_GENERIC_NAME));
+ kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
+ FLAGS_GENERIC_NAME));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflatePhoto(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true));
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateNote(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Note.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE,
+ R.string.label_notes, R.drawable.sym_note, 110, true));
+ kind.secondary = true;
+ kind.actionHeader = new SimpleInflater(R.string.label_notes);
+ kind.actionBody = new SimpleInflater(Note.NOTE);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
+ }
+
+ return kind;
+ }
+
+ protected DataKind inflateWebsite(int inflateLevel) {
+ DataKind kind = getKindForMimetype(Website.CONTENT_ITEM_TYPE);
+ if (kind == null) {
+ kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
+ R.string.websiteLabelsGroup, -1, 120, true));
+ kind.secondary = true;
+ kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
+ kind.actionBody = new SimpleInflater(Website.URL);
+ }
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
+ }
+
+ return kind;
+ }
+
+ /**
+ * Simple inflater that assumes a string resource has a "%s" that will be
+ * filled from the given column.
+ */
+ public static class SimpleInflater implements StringInflater {
+ private final int mStringRes;
+ private final String mColumnName;
+
+ public SimpleInflater(int stringRes) {
+ this(stringRes, null);
+ }
+
+ public SimpleInflater(String columnName) {
+ this(-1, columnName);
+ }
+
+ public SimpleInflater(int stringRes, String columnName) {
+ mStringRes = stringRes;
+ mColumnName = columnName;
+ }
+
+ public CharSequence inflateUsing(Context context, Cursor cursor) {
+ final int index = mColumnName != null ? cursor.getColumnIndex(mColumnName) : -1;
+ final boolean validString = mStringRes > 0;
+ final boolean validColumn = index != -1;
+
+ final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
+ final CharSequence columnValue = validColumn ? cursor.getString(index) : null;
+
+ if (validString && validColumn) {
+ return String.format(stringValue.toString(), columnValue);
+ } else if (validString) {
+ return stringValue;
+ } else if (validColumn) {
+ return columnValue;
+ } else {
+ return null;
+ }
+ }
+
+ public CharSequence inflateUsing(Context context, ContentValues values) {
+ final boolean validColumn = values.containsKey(mColumnName);
+ final boolean validString = mStringRes > 0;
+
+ final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
+ final CharSequence columnValue = validColumn ? values.getAsString(mColumnName) : null;
+
+ if (validString && validColumn) {
+ return String.format(stringValue.toString(), columnValue);
+ } else if (validString) {
+ return stringValue;
+ } else if (validColumn) {
+ return columnValue;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public static abstract class CommonInflater implements StringInflater {
+ protected abstract int getTypeLabelResource(Integer type);
+
+ protected boolean isCustom(Integer type) {
+ return type == BaseTypes.TYPE_CUSTOM;
+ }
+
+ protected CharSequence getTypeLabel(Resources res, Integer type, CharSequence label) {
+ final int labelRes = getTypeLabelResource(type);
+ if (type == null) {
+ return res.getText(labelRes);
+ } else if (isCustom(type)) {
+ return res.getString(labelRes, label == null ? "" : label);
+ } else {
+ return res.getText(labelRes);
+ }
+ }
+
+ public CharSequence inflateUsing(Context context, Cursor cursor) {
+ final Integer type = cursor.getInt(cursor.getColumnIndex(Phone.TYPE));
+ final String label = cursor.getString(cursor.getColumnIndex(Phone.LABEL));
+ return getTypeLabel(context.getResources(), type, label);
+ }
+
+ public CharSequence inflateUsing(Context context, ContentValues values) {
+ final Integer type = values.getAsInteger(Phone.TYPE);
+ final String label = values.getAsString(Phone.LABEL);
+ return getTypeLabel(context.getResources(), type, label);
+ }
+ }
+
+ public static class PhoneActionInflater extends CommonInflater {
+ @Override
+ protected boolean isCustom(Integer type) {
+ return type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT;
+ }
+
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.call_other;
+ switch (type) {
+ case Phone.TYPE_HOME: return R.string.call_home;
+ case Phone.TYPE_MOBILE: return R.string.call_mobile;
+ case Phone.TYPE_WORK: return R.string.call_work;
+ case Phone.TYPE_FAX_WORK: return R.string.call_fax_work;
+ case Phone.TYPE_FAX_HOME: return R.string.call_fax_home;
+ case Phone.TYPE_PAGER: return R.string.call_pager;
+ case Phone.TYPE_OTHER: return R.string.call_other;
+ case Phone.TYPE_CALLBACK: return R.string.call_callback;
+ case Phone.TYPE_CAR: return R.string.call_car;
+ case Phone.TYPE_COMPANY_MAIN: return R.string.call_company_main;
+ case Phone.TYPE_ISDN: return R.string.call_isdn;
+ case Phone.TYPE_MAIN: return R.string.call_main;
+ case Phone.TYPE_OTHER_FAX: return R.string.call_other_fax;
+ case Phone.TYPE_RADIO: return R.string.call_radio;
+ case Phone.TYPE_TELEX: return R.string.call_telex;
+ case Phone.TYPE_TTY_TDD: return R.string.call_tty_tdd;
+ case Phone.TYPE_WORK_MOBILE: return R.string.call_work_mobile;
+ case Phone.TYPE_WORK_PAGER: return R.string.call_work_pager;
+ case Phone.TYPE_ASSISTANT: return R.string.call_assistant;
+ case Phone.TYPE_MMS: return R.string.call_mms;
+ default: return R.string.call_custom;
+ }
+ }
+ }
+
+ public static class PhoneActionAltInflater extends CommonInflater {
+ @Override
+ protected boolean isCustom(Integer type) {
+ return (type == Phone.TYPE_CUSTOM || type == Phone.TYPE_ASSISTANT);
+ }
+
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.sms_other;
+ switch (type) {
+ case Phone.TYPE_HOME: return R.string.sms_home;
+ case Phone.TYPE_MOBILE: return R.string.sms_mobile;
+ case Phone.TYPE_WORK: return R.string.sms_work;
+ case Phone.TYPE_FAX_WORK: return R.string.sms_fax_work;
+ case Phone.TYPE_FAX_HOME: return R.string.sms_fax_home;
+ case Phone.TYPE_PAGER: return R.string.sms_pager;
+ case Phone.TYPE_OTHER: return R.string.sms_other;
+ case Phone.TYPE_CALLBACK: return R.string.sms_callback;
+ case Phone.TYPE_CAR: return R.string.sms_car;
+ case Phone.TYPE_COMPANY_MAIN: return R.string.sms_company_main;
+ case Phone.TYPE_ISDN: return R.string.sms_isdn;
+ case Phone.TYPE_MAIN: return R.string.sms_main;
+ case Phone.TYPE_OTHER_FAX: return R.string.sms_other_fax;
+ case Phone.TYPE_RADIO: return R.string.sms_radio;
+ case Phone.TYPE_TELEX: return R.string.sms_telex;
+ case Phone.TYPE_TTY_TDD: return R.string.sms_tty_tdd;
+ case Phone.TYPE_WORK_MOBILE: return R.string.sms_work_mobile;
+ case Phone.TYPE_WORK_PAGER: return R.string.sms_work_pager;
+ case Phone.TYPE_ASSISTANT: return R.string.sms_assistant;
+ case Phone.TYPE_MMS: return R.string.sms_mms;
+ default: return R.string.sms_custom;
+ }
+ }
+ }
+
+ public static class EmailActionInflater extends CommonInflater {
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.email;
+ switch (type) {
+ case Email.TYPE_HOME: return R.string.email_home;
+ case Email.TYPE_WORK: return R.string.email_work;
+ case Email.TYPE_OTHER: return R.string.email_other;
+ case Email.TYPE_MOBILE: return R.string.email_mobile;
+ default: return R.string.email_custom;
+ }
+ }
+ }
+
+ public static class PostalActionInflater extends CommonInflater {
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.map_other;
+ switch (type) {
+ case StructuredPostal.TYPE_HOME: return R.string.map_home;
+ case StructuredPostal.TYPE_WORK: return R.string.map_work;
+ case StructuredPostal.TYPE_OTHER: return R.string.map_other;
+ default: return R.string.map_custom;
+ }
+ }
+ }
+
+ public static class ImActionInflater extends CommonInflater {
+ @Override
+ protected int getTypeLabelResource(Integer type) {
+ if (type == null) return R.string.chat;
+ switch (type) {
+ case Im.PROTOCOL_AIM: return R.string.chat_aim;
+ case Im.PROTOCOL_MSN: return R.string.chat_msn;
+ case Im.PROTOCOL_YAHOO: return R.string.chat_yahoo;
+ case Im.PROTOCOL_SKYPE: return R.string.chat_skype;
+ case Im.PROTOCOL_QQ: return R.string.chat_qq;
+ case Im.PROTOCOL_GOOGLE_TALK: return R.string.chat_gtalk;
+ case Im.PROTOCOL_ICQ: return R.string.chat_icq;
+ case Im.PROTOCOL_JABBER: return R.string.chat_jabber;
+ case Im.PROTOCOL_NETMEETING: return R.string.chat;
+ default: return R.string.chat;
+ }
+ }
+ }
+}
diff --git a/src/com/android/contacts/model/GoogleSource.java b/src/com/android/contacts/model/GoogleSource.java
new file mode 100644
index 0000000..46d0623
--- /dev/null
+++ b/src/com/android/contacts/model/GoogleSource.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2009 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.model;
+
+import com.android.contacts.R;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.google.android.collect.Lists;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts.Data;
+
+import java.util.ArrayList;
+
+public class GoogleSource extends FallbackSource {
+ public static final String ACCOUNT_TYPE = "com.google.GAIA";
+
+ public GoogleSource(String resPackageName) {
+ this.accountType = ACCOUNT_TYPE;
+ this.resPackageName = null;
+ this.summaryResPackageName = resPackageName;
+ }
+
+ @Override
+ protected void inflate(Context context, int inflateLevel) {
+
+ inflateStructuredName(inflateLevel);
+ inflateNickname(inflateLevel);
+ inflatePhone(inflateLevel);
+ inflateEmail(inflateLevel);
+ inflateStructuredPostal(inflateLevel);
+ inflateIm(inflateLevel);
+ inflateOrganization(inflateLevel);
+ inflatePhoto(inflateLevel);
+ inflateNote(inflateLevel);
+ inflateWebsite(inflateLevel);
+
+ // TODO: GOOGLE: GROUPMEMBERSHIP
+
+ setInflatedLevel(inflateLevel);
+
+ }
+
+ @Override
+ protected DataKind inflateStructuredName(int inflateLevel) {
+ return super.inflateStructuredName(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateNickname(int inflateLevel) {
+ return super.inflateNickname(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflatePhone(int inflateLevel) {
+ final DataKind kind = super.inflatePhone(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildPhoneType(Phone.TYPE_HOME));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_MOBILE));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_WORK));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_WORK).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_FAX_HOME).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_PAGER).setSecondary(true));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_OTHER));
+ kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+ Phone.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateEmail(int inflateLevel) {
+ final DataKind kind = super.inflateEmail(ContactsSource.LEVEL_MIMETYPES);
+
+ if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+ kind.typeColumn = Email.TYPE;
+ kind.typeList = Lists.newArrayList();
+ kind.typeList.add(buildEmailType(Email.TYPE_HOME));
+ kind.typeList.add(buildEmailType(Email.TYPE_WORK));
+ kind.typeList.add(buildEmailType(Email.TYPE_OTHER));
+ kind.typeList.add(buildEmailType(Email.TYPE_CUSTOM).setSecondary(true).setCustomColumn(
+ Email.LABEL));
+
+ kind.fieldList = Lists.newArrayList();
+ kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
+ }
+
+ return kind;
+ }
+
+ @Override
+ protected DataKind inflateStructuredPostal(int inflateLevel) {
+ return super.inflateStructuredPostal(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateIm(int inflateLevel) {
+ return super.inflateIm(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateOrganization(int inflateLevel) {
+ return super.inflateOrganization(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflatePhoto(int inflateLevel) {
+ return super.inflatePhoto(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateNote(int inflateLevel) {
+ return super.inflateNote(inflateLevel);
+ }
+
+ @Override
+ protected DataKind inflateWebsite(int inflateLevel) {
+ return super.inflateWebsite(inflateLevel);
+ }
+
+ // TODO: this should come from resource in the future
+ private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
+
+ public static final void attemptMyContactsMembership(EntityDelta state, Context context) {
+ attemptMyContactsMembership(state, context, true);
+ }
+
+ /**
+ *
+ * @param allowRecur If the group is created between querying/about to create, we recur. But
+ * to prevent excess recursion, we provide a flag to make sure we only do the recursion loop
+ * once
+ */
+ private static final void attemptMyContactsMembership(EntityDelta state, Context context,
+ boolean allowRecur) {
+ final ContentResolver resolver = context.getContentResolver();
+ final ValuesDelta stateValues = state.getValues();
+ final String accountName = stateValues.getAsString(RawContacts.ACCOUNT_NAME);
+ final String accountType = stateValues.getAsString(RawContacts.ACCOUNT_TYPE);
+
+ Cursor cursor = resolver.query(Groups.CONTENT_URI,
+ new String[] {Groups.TITLE, Groups.SOURCE_ID, Groups.SHOULD_SYNC},
+ Groups.ACCOUNT_NAME + " =? AND " + Groups.ACCOUNT_TYPE + " =?",
+ new String[] {accountName, accountType}, null);
+
+ boolean myContactsExists = false;
+ long assignToGroupSourceId = -1;
+ while (cursor.moveToNext()) {
+ if (GOOGLE_MY_CONTACTS_GROUP.equals(cursor.getString(0))) {
+ myContactsExists = true;
+ }
+ if (assignToGroupSourceId == -1 && cursor.getInt(2) != 0) {
+ assignToGroupSourceId = cursor.getInt(1);
+ }
+
+ if (myContactsExists && assignToGroupSourceId != -1) {
+ break;
+ }
+ }
+
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
+
+ if (!myContactsExists) {
+ // create the group if it doesn't exist
+ final ContentValues newGroup = new ContentValues();
+ newGroup.put(Groups.TITLE, GOOGLE_MY_CONTACTS_GROUP);
+
+ newGroup.put(Groups.ACCOUNT_NAME, accountName);
+ newGroup.put(Groups.ACCOUNT_TYPE, accountType);
+ newGroup.put(Groups.GROUP_VISIBLE, "1");
+
+ ArrayList<ContentProviderOperation> operations =
+ new ArrayList<ContentProviderOperation>();
+
+ operations.add(ContentProviderOperation
+ .newAssertQuery(Groups.CONTENT_URI)
+ .withSelection(Groups.TITLE + "=?",
+ new String[] { GOOGLE_MY_CONTACTS_GROUP })
+ .withExpectedCount(0).build());
+ operations.add(ContentProviderOperation
+
+ .newInsert(Groups.CONTENT_URI)
+ .withValues(newGroup)
+ .build());
+ try {
+ ContentProviderResult[] results = resolver.applyBatch(
+ ContactsContract.AUTHORITY, operations);
+ values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(results[1].uri));
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Problem querying for groups", e);
+ } catch (OperationApplicationException e) {
+ // the group was created after the query but before we tried to create it
+ if (allowRecur) {
+ attemptMyContactsMembership(state, context, false);
+ }
+ return;
+ }
+ } else {
+ if (assignToGroupSourceId != -1) {
+ values.put(GroupMembership.GROUP_SOURCE_ID, assignToGroupSourceId);
+ } else {
+ // there are no Groups to add this contact to, so don't apply any membership
+ // TODO: alert user that their contact will be dropped?
+ }
+ }
+ state.addEntry(ValuesDelta.fromAfter(values));
+ } finally {
+ cursor.close();
+ }
+ }
+}
diff --git a/src/com/android/contacts/model/HardCodedSources.java b/src/com/android/contacts/model/HardCodedSources.java
deleted file mode 100644
index 503eed8..0000000
--- a/src/com/android/contacts/model/HardCodedSources.java
+++ /dev/null
@@ -1,1009 +0,0 @@
-/*
- * Copyright (C) 2009 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.model;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.provider.ContactsContract.Contacts.Data;
-import android.view.inputmethod.EditorInfo;
-
-import com.google.android.collect.Lists;
-
-import com.android.contacts.R;
-import com.android.contacts.model.ContactsSource.DataKind;
-import com.android.contacts.model.ContactsSource.EditField;
-import com.android.contacts.model.ContactsSource.EditType;
-import com.android.contacts.model.ContactsSource.StringInflater;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-
-import java.util.ArrayList;
-
-/**
- * Hard-coded definition of some {@link ContactsSource} constraints, since the
- * XML language hasn't been finalized.
- */
-public class HardCodedSources {
- // TODO: finish hard-coding all constraints
-
- public static final String ACCOUNT_TYPE_GOOGLE = "com.google.GAIA";
- public static final String ACCOUNT_TYPE_EXCHANGE = "com.android.exchange";
- public static final String ACCOUNT_TYPE_FACEBOOK = "com.facebook.auth.login";
- public static final String ACCOUNT_TYPE_FALLBACK = "com.example.fallback-contacts";
-
- private static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
- private static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
- private static final int FLAGS_PERSON_NAME = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME;
- private static final int FLAGS_PHONETIC = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_PHONETIC;
- private static final int FLAGS_GENERIC_NAME = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
- private static final int FLAGS_NOTE = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
- private static final int FLAGS_WEBSITE = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_URI;
- private static final int FLAGS_POSTAL = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
- | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
-
- private HardCodedSources() {
- // Static utility class
- }
-
- /**
- * Hard-coded instance of {@link ContactsSource} for fallback use.
- */
- static void buildFallback(Context context, ContactsSource list) {
- {
- // FALLBACK: STRUCTUREDNAME
- DataKind kind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
- R.string.nameLabelsGroup, -1, -1, true);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
- FLAGS_PERSON_NAME));
- kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
- FLAGS_PERSON_NAME));
-
- list.add(kind);
- }
-
- {
- // FALLBACK: PHONE
- DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE,
- R.string.phoneLabelsGroup, android.R.drawable.sym_action_call, 10, true);
- kind.iconAltRes = R.drawable.sym_action_sms;
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionAltHeader = new ActionAltInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Phone.NUMBER);
-
- kind.typeColumn = Phone.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(Phone.TYPE_HOME, R.string.type_home, R.string.call_home,
- R.string.sms_home));
- kind.typeList.add(new EditType(Phone.TYPE_MOBILE, R.string.type_mobile,
- R.string.call_mobile, R.string.sms_mobile));
- kind.typeList.add(new EditType(Phone.TYPE_WORK, R.string.type_work, R.string.call_work,
- R.string.sms_work));
- kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, R.string.type_fax_work,
- R.string.call_fax_work, R.string.sms_fax_work).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_FAX_HOME, R.string.type_fax_home,
- R.string.call_fax_home, R.string.sms_fax_home).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_PAGER, R.string.type_pager,
- R.string.call_pager, R.string.sms_pager).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_OTHER, R.string.type_other,
- R.string.call_other, R.string.sms_other));
- kind.typeList.add(new EditType(Phone.TYPE_CUSTOM, R.string.type_custom,
- R.string.call_custom, R.string.sms_custom).setSecondary(true).setCustomColumn(
- Phone.LABEL));
- kind.typeList.add(new EditType(Phone.TYPE_CAR, R.string.type_car, R.string.call_car,
- R.string.sms_car).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_COMPANY_MAIN, R.string.type_company_main,
- R.string.call_company_main, R.string.sms_company_main).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_MMS, R.string.type_mms, R.string.call_mms,
- R.string.sms_mms).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_RADIO, R.string.type_radio, R.string.call_radio,
- R.string.sms_radio).setSecondary(true));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
-
- list.add(kind);
- }
-
- {
- // FALLBACK: POSTAL
- DataKind kind = new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
- R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- // TODO: build body from various structured fields
- kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
-
- kind.typeColumn = StructuredPostal.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(StructuredPostal.TYPE_HOME, R.string.type_home,
- R.string.map_home));
- kind.typeList.add(new EditType(StructuredPostal.TYPE_WORK, R.string.type_work,
- R.string.map_work));
- kind.typeList.add(new EditType(StructuredPostal.TYPE_OTHER, R.string.type_other,
- R.string.map_other));
- kind.typeList
- .add(new EditType(StructuredPostal.TYPE_CUSTOM, R.string.type_custom,
- R.string.map_custom).setSecondary(true).setCustomColumn(
- StructuredPostal.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POBOX, R.string.postal_pobox,
- FLAGS_POSTAL, true));
- kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
- R.string.postal_neighborhood, FLAGS_POSTAL, true));
- kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
- FLAGS_POSTAL, true));
-
- list.add(kind);
- }
-
- {
- // FALLBACK: EMAIL
- DataKind kind = new DataKind(Email.CONTENT_ITEM_TYPE,
- R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Email.DATA);
-
- kind.typeColumn = Email.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList
- .add(new EditType(Email.TYPE_HOME, R.string.type_home, R.string.email_home));
- kind.typeList
- .add(new EditType(Email.TYPE_WORK, R.string.type_work, R.string.email_work));
- kind.typeList.add(new EditType(Email.TYPE_OTHER, R.string.type_other,
- R.string.email_other));
- kind.typeList.add(new EditType(Email.TYPE_CUSTOM, R.string.type_custom,
- R.string.email_home).setSecondary(true).setCustomColumn(Email.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
-
- list.add(kind);
- }
- }
-
- /**
- * Hard-coded instance of {@link ContactsSource} for Google Contacts.
- */
- static void buildGoogle(Context context, ContactsSource list) {
- {
- // GOOGLE: STRUCTUREDNAME
- DataKind kind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
- R.string.nameLabelsGroup, -1, -1, true);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
- FLAGS_PERSON_NAME, true));
- kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
- FLAGS_PERSON_NAME));
- kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
- FLAGS_PERSON_NAME, true));
- kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
- FLAGS_PERSON_NAME));
- kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
- FLAGS_PERSON_NAME, true));
- kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
- R.string.name_phonetic_given, FLAGS_PHONETIC, true));
- kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
- R.string.name_phonetic_middle, FLAGS_PHONETIC, true));
- kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
- R.string.name_phonetic_family, FLAGS_PHONETIC, true));
-
- list.add(kind);
- }
-
- {
- // GOOGLE: PHOTO
- DataKind kind = new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
-
- list.add(kind);
- }
-
- {
- // GOOGLE: PHONE
- DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE,
- R.string.phoneLabelsGroup, android.R.drawable.sym_action_call, 10, true);
- kind.iconAltRes = R.drawable.sym_action_sms;
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionAltHeader = new ActionAltInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Phone.NUMBER);
-
- kind.typeColumn = Phone.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(Phone.TYPE_HOME, R.string.type_home, R.string.call_home,
- R.string.sms_home));
- kind.typeList.add(new EditType(Phone.TYPE_MOBILE, R.string.type_mobile,
- R.string.call_mobile, R.string.sms_mobile));
- kind.typeList.add(new EditType(Phone.TYPE_WORK, R.string.type_work, R.string.call_work,
- R.string.sms_work));
- kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, R.string.type_fax_work,
- R.string.call_fax_work, R.string.sms_fax_work).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_FAX_HOME, R.string.type_fax_home,
- R.string.call_fax_home, R.string.sms_fax_home).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_PAGER, R.string.type_pager,
- R.string.call_pager, R.string.sms_pager).setSecondary(true));
- kind.typeList.add(new EditType(Phone.TYPE_OTHER, R.string.type_other,
- R.string.call_other, R.string.sms_other));
- kind.typeList.add(new EditType(Phone.TYPE_CUSTOM, R.string.type_custom,
- R.string.call_custom, R.string.sms_custom).setSecondary(true).setCustomColumn(
- Phone.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
-
- list.add(kind);
- }
-
- {
- // GOOGLE: EMAIL
- DataKind kind = new DataKind(Email.CONTENT_ITEM_TYPE,
- R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Email.DATA);
-
- kind.typeColumn = Email.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList
- .add(new EditType(Email.TYPE_HOME, R.string.type_home, R.string.email_home));
- kind.typeList
- .add(new EditType(Email.TYPE_WORK, R.string.type_work, R.string.email_work));
- kind.typeList.add(new EditType(Email.TYPE_OTHER, R.string.type_other,
- R.string.email_other));
- kind.typeList.add(new EditType(Email.TYPE_CUSTOM, R.string.type_custom,
- R.string.email_home).setSecondary(true).setCustomColumn(Email.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
-
- list.add(kind);
- }
-
- {
- // GOOGLE: IM
- DataKind kind = new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
- android.R.drawable.sym_action_chat, 20, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Im.DATA);
-
- // NOTE: even though a traditional "type" exists, for editing
- // purposes we're using the protocol to pick labels
-
- kind.defaultValues = new ContentValues();
- kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
-
- kind.typeColumn = Im.PROTOCOL;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(Im.PROTOCOL_AIM, R.string.type_im_aim,
- R.string.chat_aim));
- kind.typeList.add(new EditType(Im.PROTOCOL_MSN, R.string.type_im_msn,
- R.string.chat_msn));
- kind.typeList.add(new EditType(Im.PROTOCOL_YAHOO, R.string.type_im_yahoo,
- R.string.chat_yahoo));
- kind.typeList.add(new EditType(Im.PROTOCOL_SKYPE, R.string.type_im_skype,
- R.string.chat_skype));
- kind.typeList.add(new EditType(Im.PROTOCOL_QQ, R.string.type_im_qq, R.string.chat_qq));
- kind.typeList.add(new EditType(Im.PROTOCOL_GOOGLE_TALK, R.string.type_im_google_talk,
- R.string.chat_gtalk));
- kind.typeList.add(new EditType(Im.PROTOCOL_ICQ, R.string.type_im_icq,
- R.string.chat_icq));
- kind.typeList.add(new EditType(Im.PROTOCOL_JABBER, R.string.type_im_jabber,
- R.string.chat_jabber));
- kind.typeList.add(new EditType(Im.PROTOCOL_CUSTOM, R.string.type_custom,
- R.string.chat_other).setSecondary(true).setCustomColumn(Im.CUSTOM_PROTOCOL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
-
- list.add(kind);
- }
-
- {
- // GOOGLE: POSTAL
- DataKind kind = new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
- R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- // TODO: build body from various structured fields
- kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
-
- kind.typeColumn = StructuredPostal.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(StructuredPostal.TYPE_HOME, R.string.type_home,
- R.string.map_home));
- kind.typeList.add(new EditType(StructuredPostal.TYPE_WORK, R.string.type_work,
- R.string.map_work));
- kind.typeList.add(new EditType(StructuredPostal.TYPE_OTHER, R.string.type_other,
- R.string.map_other));
- kind.typeList
- .add(new EditType(StructuredPostal.TYPE_CUSTOM, R.string.type_custom,
- R.string.map_custom).setSecondary(true).setCustomColumn(
- StructuredPostal.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POBOX, R.string.postal_pobox,
- FLAGS_POSTAL, true));
- kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
- R.string.postal_neighborhood, FLAGS_POSTAL, true));
- kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
- FLAGS_POSTAL, true));
-
- list.add(kind);
- }
-
- {
- // GOOGLE: ORGANIZATION
- DataKind kind = new DataKind(Organization.CONTENT_ITEM_TYPE,
- R.string.organizationLabelsGroup, R.drawable.sym_action_organization, 30, true);
-
- kind.actionHeader = new SimpleInflater(Organization.COMPANY);
- // TODO: build body from multiple fields
- kind.actionBody = new SimpleInflater(Organization.TITLE);
-
- kind.typeColumn = Organization.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(Organization.TYPE_WORK, R.string.type_work));
- kind.typeList.add(new EditType(Organization.TYPE_OTHER, R.string.type_other));
- kind.typeList.add(new EditType(Organization.TYPE_CUSTOM, R.string.type_custom)
- .setSecondary(true).setCustomColumn(Organization.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
- FLAGS_GENERIC_NAME));
- kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
- FLAGS_GENERIC_NAME));
-
- list.add(kind);
- }
-
- {
- // GOOGLE: NOTE
- DataKind kind = new DataKind(Note.CONTENT_ITEM_TYPE,
- R.string.label_notes, R.drawable.sym_note, 110, true);
- kind.secondary = true;
-
- kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.label_notes);
- kind.actionBody = new SimpleInflater(Note.NOTE);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
-
- list.add(kind);
- }
-
- {
- // GOOGLE: NICKNAME
- DataKind kind = new DataKind(Nickname.CONTENT_ITEM_TYPE,
- R.string.nicknameLabelsGroup, -1, 115, true);
- kind.secondary = true;
-
- kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.nicknameLabelsGroup);
- kind.actionBody = new SimpleInflater(Nickname.NAME);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
- FLAGS_PERSON_NAME));
-
- list.add(kind);
- }
-
- // TODO: GOOGLE: GROUPMEMBERSHIP
-
- {
- // GOOGLE: WEBSITE
- DataKind kind = new DataKind(Website.CONTENT_ITEM_TYPE,
- R.string.websiteLabelsGroup, -1, 120, true);
- kind.secondary = true;
-
- kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.websiteLabelsGroup);
- kind.actionBody = new SimpleInflater(Website.URL);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
-
- list.add(kind);
- }
- }
-
- // TODO: this should come from resource in the future
- private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
-
- public static final void attemptMyContactsMembership(EntityDelta state, Context context) {
- attemptMyContactsMembership(state, context, true);
- }
-
- /**
- *
- * @param allowRecur If the group is created between querying/about to create, we recur. But
- * to prevent excess recursion, we provide a flag to make sure we only do the recursion loop
- * once
- */
- private static final void attemptMyContactsMembership(EntityDelta state, Context context,
- boolean allowRecur) {
- final ContentResolver resolver = context.getContentResolver();
- final ValuesDelta stateValues = state.getValues();
- final String accountName = stateValues.getAsString(RawContacts.ACCOUNT_NAME);
- final String accountType = stateValues.getAsString(RawContacts.ACCOUNT_TYPE);
-
- Cursor cursor = resolver.query(Groups.CONTENT_URI,
- new String[] {Groups.TITLE, Groups.SOURCE_ID, Groups.SHOULD_SYNC},
- Groups.ACCOUNT_NAME + " =? AND " + Groups.ACCOUNT_TYPE + " =?",
- new String[] {accountName, accountType}, null);
-
- boolean myContactsExists = false;
- long assignToGroupSourceId = -1;
- while (cursor.moveToNext()) {
- if (GOOGLE_MY_CONTACTS_GROUP.equals(cursor.getString(0))) {
- myContactsExists = true;
- }
- if (assignToGroupSourceId == -1 && cursor.getInt(2) != 0) {
- assignToGroupSourceId = cursor.getInt(1);
- }
-
- if (myContactsExists && assignToGroupSourceId != -1) {
- break;
- }
- }
-
- try {
- final ContentValues values = new ContentValues();
- values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
-
- if (!myContactsExists) {
- // create the group if it doesn't exist
- final ContentValues newGroup = new ContentValues();
- newGroup.put(Groups.TITLE, GOOGLE_MY_CONTACTS_GROUP);
-
- newGroup.put(Groups.ACCOUNT_NAME, accountName);
- newGroup.put(Groups.ACCOUNT_TYPE, accountType);
- newGroup.put(Groups.GROUP_VISIBLE, "1");
-
- ArrayList<ContentProviderOperation> operations =
- new ArrayList<ContentProviderOperation>();
-
- operations.add(ContentProviderOperation
- .newAssertQuery(Groups.CONTENT_URI)
- .withSelection(Groups.TITLE + "=?",
- new String[] { GOOGLE_MY_CONTACTS_GROUP })
- .withExpectedCount(0).build());
- operations.add(ContentProviderOperation
-
- .newInsert(Groups.CONTENT_URI)
- .withValues(newGroup)
- .build());
- try {
- ContentProviderResult[] results = resolver.applyBatch(
- ContactsContract.AUTHORITY, operations);
- values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(results[1].uri));
- } catch (RemoteException e) {
- throw new IllegalStateException("Problem querying for groups", e);
- } catch (OperationApplicationException e) {
- // the group was created after the query but before we tried to create it
- if (allowRecur) {
- attemptMyContactsMembership(state, context, false);
- }
- return;
- }
- } else {
- if (assignToGroupSourceId != -1) {
- values.put(GroupMembership.GROUP_SOURCE_ID, assignToGroupSourceId);
- } else {
- // there are no Groups to add this contact to, so don't apply any membership
- // TODO: alert user that their contact will be dropped?
- }
- }
- state.addEntry(ValuesDelta.fromAfter(values));
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Hard-coded instance of {@link ContactsSource} for Exchange.
- */
- static void buildExchange(Context context, ContactsSource list) {
- {
- // EXCHANGE: STRUCTUREDNAME
- DataKind kind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
- R.string.nameLabelsGroup, -1, -1, true);
- kind.typeOverallMax = 1;
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
- FLAGS_PERSON_NAME, true));
- kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
- FLAGS_PERSON_NAME));
- kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
- FLAGS_PERSON_NAME, true));
- kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
- FLAGS_PERSON_NAME));
- kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
- FLAGS_PERSON_NAME, true));
- kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
- R.string.name_phonetic_given, FLAGS_PHONETIC, true));
- kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
- R.string.name_phonetic_family, FLAGS_PHONETIC, true));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: PHOTO
- DataKind kind = new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true);
- kind.typeOverallMax = 1;
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: PHONE
- DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE,
- R.string.phoneLabelsGroup, android.R.drawable.sym_action_call, 10, true);
- kind.iconAltRes = R.drawable.sym_action_sms;
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionAltHeader = new ActionAltInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Phone.NUMBER);
-
- kind.typeColumn = Phone.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(Phone.TYPE_HOME, R.string.type_home, R.string.call_home,
- R.string.sms_home).setSpecificMax(2));
- kind.typeList.add(new EditType(Phone.TYPE_MOBILE, R.string.type_mobile,
- R.string.call_mobile, R.string.sms_mobile).setSpecificMax(1));
- kind.typeList.add(new EditType(Phone.TYPE_WORK, R.string.type_work, R.string.call_work,
- R.string.sms_work).setSpecificMax(2));
- kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, R.string.type_fax_work,
- R.string.call_fax_work, R.string.sms_fax_work).setSecondary(true)
- .setSpecificMax(1));
- kind.typeList.add(new EditType(Phone.TYPE_FAX_HOME, R.string.type_fax_home,
- R.string.call_fax_home, R.string.sms_fax_home).setSecondary(true)
- .setSpecificMax(1));
- kind.typeList.add(new EditType(Phone.TYPE_PAGER, R.string.type_pager,
- R.string.call_pager, R.string.sms_pager).setSecondary(true).setSpecificMax(1));
- kind.typeList.add(new EditType(Phone.TYPE_CAR, R.string.type_car, R.string.call_car,
- R.string.sms_car).setSecondary(true).setSpecificMax(1));
- kind.typeList.add(new EditType(Phone.TYPE_COMPANY_MAIN, R.string.type_company_main,
- R.string.call_company_main, R.string.sms_company_main).setSecondary(true)
- .setSpecificMax(1));
- kind.typeList.add(new EditType(Phone.TYPE_MMS, R.string.type_mms, R.string.call_mms,
- R.string.sms_mms).setSecondary(true).setSpecificMax(1));
- kind.typeList.add(new EditType(Phone.TYPE_RADIO, R.string.type_radio,
- R.string.call_radio, R.string.sms_radio).setSecondary(true).setSpecificMax(1));
- kind.typeList.add(new EditType(Phone.TYPE_CUSTOM, R.string.type_assistant,
- R.string.call_custom, R.string.sms_custom).setSecondary(true).setSpecificMax(1)
- .setCustomColumn(Phone.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Phone.NUMBER, R.string.phoneLabelsGroup, FLAGS_PHONE));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: EMAIL
- DataKind kind = new DataKind(Email.CONTENT_ITEM_TYPE,
- R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Email.DATA);
- kind.typeOverallMax = 3;
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: IM
- DataKind kind = new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
- android.R.drawable.sym_action_chat, 20, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Im.DATA);
- kind.typeOverallMax = 3;
-
- // NOTE: even though a traditional "type" exists, for editing
- // purposes we're using the protocol to pick labels
-
- kind.defaultValues = new ContentValues();
- kind.defaultValues.put(Im.TYPE, Im.TYPE_OTHER);
-
- kind.typeColumn = Im.PROTOCOL;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(Im.PROTOCOL_AIM, R.string.type_im_aim,
- R.string.chat_aim));
- kind.typeList.add(new EditType(Im.PROTOCOL_MSN, R.string.type_im_msn,
- R.string.chat_msn));
- kind.typeList.add(new EditType(Im.PROTOCOL_YAHOO, R.string.type_im_yahoo,
- R.string.chat_yahoo));
- kind.typeList.add(new EditType(Im.PROTOCOL_SKYPE, R.string.type_im_skype,
- R.string.chat_skype));
- kind.typeList.add(new EditType(Im.PROTOCOL_QQ, R.string.type_im_qq, R.string.chat_qq));
- kind.typeList.add(new EditType(Im.PROTOCOL_GOOGLE_TALK, R.string.type_im_google_talk,
- R.string.chat_gtalk));
- kind.typeList.add(new EditType(Im.PROTOCOL_ICQ, R.string.type_im_icq,
- R.string.chat_icq));
- kind.typeList.add(new EditType(Im.PROTOCOL_JABBER, R.string.type_im_jabber,
- R.string.chat_jabber));
- kind.typeList.add(new EditType(Im.PROTOCOL_CUSTOM, R.string.type_custom,
- R.string.chat_other).setSecondary(true).setCustomColumn(Im.CUSTOM_PROTOCOL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Im.DATA, R.string.imLabelsGroup, FLAGS_EMAIL));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: POSTAL
- DataKind kind = new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
- R.string.postalLabelsGroup, R.drawable.sym_action_map, 25, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- // TODO: build body from various structured fields
- kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
-
- kind.typeColumn = StructuredPostal.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(StructuredPostal.TYPE_WORK, R.string.type_work,
- R.string.map_work).setSpecificMax(1));
- kind.typeList.add(new EditType(StructuredPostal.TYPE_HOME, R.string.type_home,
- R.string.map_home).setSpecificMax(1));
- kind.typeList.add(new EditType(StructuredPostal.TYPE_OTHER, R.string.type_other,
- R.string.map_other).setSpecificMax(1));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
- FLAGS_POSTAL));
- kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
- FLAGS_POSTAL, true));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: NICKNAME
- DataKind kind = new DataKind(Nickname.CONTENT_ITEM_TYPE,
- R.string.nicknameLabelsGroup, -1, 115, true);
- kind.secondary = true;
- kind.typeOverallMax = 1;
-
- kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.nicknameLabelsGroup);
- kind.actionBody = new SimpleInflater(Nickname.NAME);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
- FLAGS_PERSON_NAME));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: WEBSITE
- DataKind kind = new DataKind(Website.CONTENT_ITEM_TYPE,
- R.string.websiteLabelsGroup, -1, 120, true);
- kind.secondary = true;
- kind.typeOverallMax = 1;
-
- kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.websiteLabelsGroup);
- kind.actionBody = new SimpleInflater(Website.URL);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: ORGANIZATION
- DataKind kind = new DataKind(Organization.CONTENT_ITEM_TYPE,
- R.string.organizationLabelsGroup, R.drawable.sym_action_organization, 30, true);
-
- kind.actionHeader = new SimpleInflater(Organization.COMPANY);
- // TODO: build body from multiple fields
- kind.actionBody = new SimpleInflater(Organization.TITLE);
-
- kind.typeColumn = Organization.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(Organization.TYPE_WORK, R.string.type_work));
- kind.typeList.add(new EditType(Organization.TYPE_OTHER, R.string.type_other));
- kind.typeList.add(new EditType(Organization.TYPE_CUSTOM, R.string.type_custom)
- .setSecondary(true).setCustomColumn(Organization.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
- FLAGS_GENERIC_NAME));
- kind.fieldList.add(new EditField(Organization.TITLE, R.string.ghostData_title,
- FLAGS_GENERIC_NAME));
-
- list.add(kind);
- }
-
- {
- // EXCHANGE: NOTE
- DataKind kind = new DataKind(Note.CONTENT_ITEM_TYPE,
- R.string.label_notes, R.drawable.sym_note, 110, true);
- kind.secondary = true;
-
- kind.actionHeader = new SimpleInflater(list.resPackageName, R.string.label_notes);
- kind.actionBody = new SimpleInflater(Note.NOTE);
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
-
- list.add(kind);
- }
- }
-
- /**
- * Hard-coded instance of {@link ContactsSource} for Facebook.
- */
- static void buildFacebook(Context context, ContactsSource list) {
- list.accountType = ACCOUNT_TYPE_FACEBOOK;
- list.readOnly = true;
-
- {
- // FACEBOOK: PHONE
- DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE,
- R.string.phoneLabelsGroup, android.R.drawable.sym_action_call, 10, true);
- kind.iconAltRes = R.drawable.sym_action_sms;
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionAltHeader = new ActionAltInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Phone.NUMBER);
-
- kind.typeColumn = Phone.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList.add(new EditType(Phone.TYPE_MOBILE, R.string.type_mobile,
- R.string.call_mobile, R.string.sms_mobile));
- kind.typeList.add(new EditType(Phone.TYPE_OTHER, R.string.type_other,
- R.string.call_other, R.string.sms_other));
-
- list.add(kind);
- }
-
- {
- // FACEBOOK: EMAIL
- DataKind kind = new DataKind(Email.CONTENT_ITEM_TYPE,
- R.string.emailLabelsGroup, android.R.drawable.sym_action_email, 15, true);
-
- kind.actionHeader = new ActionInflater(list.resPackageName, kind);
- kind.actionBody = new SimpleInflater(Email.DATA);
-
- kind.typeColumn = Email.TYPE;
- kind.typeList = Lists.newArrayList();
- kind.typeList
- .add(new EditType(Email.TYPE_HOME, R.string.type_home, R.string.email_home));
- kind.typeList
- .add(new EditType(Email.TYPE_WORK, R.string.type_work, R.string.email_work));
- kind.typeList.add(new EditType(Email.TYPE_OTHER, R.string.type_other,
- R.string.email_other));
- kind.typeList.add(new EditType(Email.TYPE_CUSTOM, R.string.type_custom,
- R.string.email_home).setSecondary(true).setCustomColumn(Email.LABEL));
-
- kind.fieldList = Lists.newArrayList();
- kind.fieldList.add(new EditField(Email.DATA, R.string.emailLabelsGroup, FLAGS_EMAIL));
-
- list.add(kind);
- }
- }
-
- /**
- * Simple inflater that assumes a string resource has a "%s" that will be
- * filled from the given column.
- */
- public static class SimpleInflater implements StringInflater {
- private final String mPackageName;
- private final int mStringRes;
- private final String mColumnName;
-
- public SimpleInflater(String packageName, int stringRes) {
- this(packageName, stringRes, null);
- }
-
- public SimpleInflater(String columnName) {
- this(null, -1, columnName);
- }
-
- public SimpleInflater(String packageName, int stringRes, String columnName) {
- mPackageName = packageName;
- mStringRes = stringRes;
- mColumnName = columnName;
- }
-
- public CharSequence inflateUsing(Context context, Cursor cursor) {
- final int index = mColumnName != null ? cursor.getColumnIndex(mColumnName) : -1;
- final boolean validString = mStringRes > 0;
- final boolean validColumn = index != -1;
-
- final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
- final CharSequence columnValue = validColumn ? cursor.getString(index) : null;
-
- if (validString && validColumn) {
- return String.format(stringValue.toString(), columnValue);
- } else if (validString) {
- return stringValue;
- } else if (validColumn) {
- return columnValue;
- } else {
- return null;
- }
- }
-
- public CharSequence inflateUsing(Context context, ContentValues values) {
- final boolean validColumn = values.containsKey(mColumnName);
- final boolean validString = mStringRes > 0;
-
- final CharSequence stringValue = validString ? context.getText(mStringRes) : null;
- final CharSequence columnValue = validColumn ? values.getAsString(mColumnName) : null;
-
- if (validString && validColumn) {
- return String.format(stringValue.toString(), columnValue);
- } else if (validString) {
- return stringValue;
- } else if (validColumn) {
- return columnValue;
- } else {
- return null;
- }
- }
- }
-
- /**
- * Simple inflater that will combine two string resources, usually to
- * provide an action string like "Call home", where "home" is provided from
- * {@link EditType#labelRes}.
- */
- public static class ActionInflater implements StringInflater {
- private String mPackageName;
- private DataKind mKind;
-
- public ActionInflater(String packageName, DataKind labelProvider) {
- mPackageName = packageName;
- mKind = labelProvider;
- }
-
- public CharSequence inflateUsing(Context context, Cursor cursor) {
- final EditType type = EntityModifier.getCurrentType(cursor, mKind);
- final boolean validString = (type != null && type.actionRes != 0);
- if (!validString) return null;
-
- if (type.customColumn != null) {
- final int index = cursor.getColumnIndex(type.customColumn);
- final String customLabel = cursor.getString(index);
- return String.format(context.getString(type.actionRes), customLabel);
- } else {
- return context.getText(type.actionRes);
- }
- }
-
- public CharSequence inflateUsing(Context context, ContentValues values) {
- final EditType type = EntityModifier.getCurrentType(values, mKind);
- final boolean validString = (type != null && type.actionRes != 0);
- if (!validString) return null;
-
- if (type.customColumn != null) {
- final String customLabel = values.getAsString(type.customColumn);
- return String.format(context.getString(type.actionRes), customLabel);
- } else {
- return context.getText(type.actionRes);
- }
- }
- }
-
- public static class ActionAltInflater implements StringInflater {
- private String mPackageName;
- private DataKind mKind;
-
- public ActionAltInflater(String packageName, DataKind labelProvider) {
- mPackageName = packageName;
- mKind = labelProvider;
- }
-
- public CharSequence inflateUsing(Context context, Cursor cursor) {
- final EditType type = EntityModifier.getCurrentType(cursor, mKind);
- final boolean validString = (type != null && type.actionAltRes != 0);
- CharSequence actionString;
- if (type.customColumn != null) {
- final int index = cursor.getColumnIndex(type.customColumn);
- final String customLabel = cursor.getString(index);
- actionString = String.format(context.getString(type.actionAltRes),
- customLabel);
- } else {
- actionString = context.getText(type.actionAltRes);
- }
- return validString ? actionString : null;
- }
-
- public CharSequence inflateUsing(Context context, ContentValues values) {
- final EditType type = EntityModifier.getCurrentType(values, mKind);
- final boolean validString = (type != null && type.actionAltRes != 0);
- CharSequence actionString;
- if (type.customColumn != null) {
- final String customLabel = values.getAsString(type.customColumn);
- actionString = String.format(context.getString(type.actionAltRes),
- customLabel);
- } else {
- actionString = context.getText(type.actionAltRes);
- }
- return validString ? actionString : null;
- }
- }
-}
diff --git a/src/com/android/contacts/model/Sources.java b/src/com/android/contacts/model/Sources.java
index 71607c7..ad9ddf1 100644
--- a/src/com/android/contacts/model/Sources.java
+++ b/src/com/android/contacts/model/Sources.java
@@ -16,38 +16,45 @@
package com.android.contacts.model;
-import com.android.contacts.R;
+import com.android.contacts.model.ContactsSource.DataKind;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentService;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SyncAdapterType;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.provider.ContactsContract;
+import android.text.TextUtils;
import android.util.Log;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
/**
* Singleton holder for all parsed {@link ContactsSource} available on the
* system, typically filled through {@link PackageManager} queries.
*/
-public class Sources {
+public class Sources extends BroadcastReceiver {
private static final String TAG = "Sources";
- public static final String ACCOUNT_TYPE_FALLBACK = HardCodedSources.ACCOUNT_TYPE_FALLBACK;
-
private Context mContext;
+ private ContactsSource mFallbackSource = null;
+
private HashMap<String, ContactsSource> mSources = Maps.newHashMap();
+ private HashSet<String> mKnownPackages = Sets.newHashSet();
private static SoftReference<Sources> sInstance = null;
@@ -70,13 +77,58 @@
*/
private Sources(Context context) {
mContext = context;
+
+ // Create fallback contacts source for on-phone contacts
+ mFallbackSource = new FallbackSource();
+
loadAccounts();
+ registerIntentReceivers(context);
}
/** @hide exposed for unit tests */
public Sources(ContactsSource... sources) {
for (ContactsSource source : sources) {
- mSources.put(source.accountType, source);
+ addSource(source);
+ }
+ }
+
+ protected void addSource(ContactsSource source) {
+ mSources.put(source.accountType, source);
+ mKnownPackages.add(source.resPackageName);
+ }
+
+ private void registerIntentReceivers(Context context) {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+
+ // We use getApplicationContext() so that the broadcast reciever can stay registered for
+ // the length of the application lifetime (instead of the calling activity's lifetime).
+ // This is so that we can notified of package changes, and purge the cache accordingly,
+ // but not be woken up if the application process isn't already running, since we will
+ // have no cache to clear at that point.
+ context.getApplicationContext().registerReceiver(this, filter);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final String packageName = intent.getData().getSchemeSpecificPart();
+
+ final boolean matchingPackage = mKnownPackages.contains(packageName);
+ final boolean validAction = Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ || Intent.ACTION_PACKAGE_ADDED.equals(action)
+ || Intent.ACTION_PACKAGE_CHANGED.equals(action);
+
+ if (matchingPackage && validAction) {
+ for (ContactsSource source : mSources.values()) {
+ if (TextUtils.equals(packageName, source.resPackageName)) {
+ // Invalidate any cache for the changed package
+ source.invalidateCache();
+ }
+ }
}
}
@@ -87,17 +139,6 @@
protected void loadAccounts() {
mSources.clear();
- {
- // Create fallback contacts source for on-phone contacts
- final ContactsSource source = new ContactsSource();
- source.accountType = HardCodedSources.ACCOUNT_TYPE_FALLBACK;
- source.resPackageName = mContext.getPackageName();
- source.titleRes = R.string.account_phone;
- source.iconRes = R.drawable.ic_launcher_contacts;
-
- mSources.put(source.accountType, source);
- }
-
final AccountManager am = AccountManager.get(mContext);
final IContentService cs = ContentResolver.getContentService();
@@ -116,14 +157,23 @@
final String accountType = sync.accountType;
final AuthenticatorDescription auth = findAuthenticator(auths, accountType);
- final ContactsSource source = new ContactsSource();
+ ContactsSource source;
+ if (GoogleSource.ACCOUNT_TYPE.equals(accountType)) {
+ source = new GoogleSource(auth.packageName);
+ } else if (ExchangeSource.ACCOUNT_TYPE.equals(accountType)) {
+ source = new ExchangeSource(auth.packageName);
+ } else {
+ // TODO: use syncadapter package instead, since it provides resources
+ Log.d(TAG, "Creating external source for type=" + accountType
+ + ", packageName=" + auth.packageName);
+ source = new ExternalSource(auth.packageName);
+ }
+
source.accountType = auth.type;
- // TODO: use syncadapter package instead, since it provides resources
- source.resPackageName = auth.packageName;
source.titleRes = auth.labelId;
source.iconRes = auth.iconId;
- mSources.put(accountType, source);
+ addSource(source);
}
} catch (RemoteException e) {
Log.w(TAG, "Problem loading accounts: " + e.toString());
@@ -166,22 +216,45 @@
return matching;
}
- protected ContactsSource getSourceForType(String accountType) {
- ContactsSource source = mSources.get(accountType);
- if (source == null) {
- Log.w(TAG, "Unknown account type '" + accountType + "', falling back to default");
- source = mSources.get(ACCOUNT_TYPE_FALLBACK);
+ /**
+ * Find the best {@link DataKind} matching the requested
+ * {@link ContactsSource#accountType} and {@link DataKind#mimeType}. If no
+ * direct match found, we try searching {@link #mFallbackSource}.
+ */
+ public DataKind getKindOrFallback(String accountType, String mimeType, Context context,
+ int inflateLevel) {
+ DataKind kind = null;
+
+ // Try finding source and kind matching request
+ final ContactsSource source = mSources.get(accountType);
+ if (source != null) {
+ source.ensureInflated(context, inflateLevel);
+ kind = source.getKindForMimetype(mimeType);
}
- return source;
+
+ if (kind == null) {
+ // Nothing found, so try fallback as last resort
+ mFallbackSource.ensureInflated(context, inflateLevel);
+ kind = mFallbackSource.getKindForMimetype(mimeType);
+ }
+
+ if (kind == null) {
+ Log.w(TAG, "Unknown type=" + accountType + ", mime=" + mimeType);
+ }
+
+ return kind;
}
/**
* Return {@link ContactsSource} for the given account type.
*/
public ContactsSource getInflatedSource(String accountType, int inflateLevel) {
- final ContactsSource source = getSourceForType(accountType);
- if (source == null || source.isInflated(inflateLevel)) {
- // Found inflated, so return directly
+ // Try finding specific source, otherwise use fallback
+ ContactsSource source = mSources.get(accountType);
+ if (source == null) source = mFallbackSource;
+
+ if (source.isInflated(inflateLevel)) {
+ // Already inflated, so return directly
return source;
} else {
// Not inflated, but requested that we force-inflate
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 6059e62..c02ee80 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -62,12 +62,12 @@
import com.android.contacts.ContactsUtils;
import com.android.contacts.R;
import com.android.contacts.ScrollingTabWidget;
+import com.android.contacts.model.GoogleSource;
import com.android.contacts.model.ContactsSource;
import com.android.contacts.model.Editor;
import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityModifier;
import com.android.contacts.model.EntitySet;
-import com.android.contacts.model.HardCodedSources;
import com.android.contacts.model.Sources;
import com.android.contacts.model.Editor.EditorListener;
import com.android.contacts.model.EntityDelta.ValuesDelta;
@@ -843,8 +843,8 @@
// Create "My Contacts" membership for Google contacts
// TODO: move this off into "templates" for each given source
- if (HardCodedSources.ACCOUNT_TYPE_GOOGLE.equals(source.accountType)) {
- HardCodedSources.attemptMyContactsMembership(insert, target);
+ if (GoogleSource.ACCOUNT_TYPE.equals(source.accountType)) {
+ GoogleSource.attemptMyContactsMembership(insert, target);
}
// TODO: no synchronization here on target.mState. This
diff --git a/src/com/android/contacts/ui/FastTrackActivity.java b/src/com/android/contacts/ui/FastTrackActivity.java
index 8fdc40d..6958298 100644
--- a/src/com/android/contacts/ui/FastTrackActivity.java
+++ b/src/com/android/contacts/ui/FastTrackActivity.java
@@ -35,6 +35,10 @@
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ if (FastTrackWindow.TRACE_LAUNCH) {
+ android.os.Debug.startMethodTracing(FastTrackWindow.TRACE_TAG);
+ }
+
// Use our local window token for now
final Intent intent = getIntent();
final Uri lookupUri = intent.getData();
diff --git a/src/com/android/contacts/ui/FastTrackWindow.java b/src/com/android/contacts/ui/FastTrackWindow.java
index bad8a21..8626818 100644
--- a/src/com/android/contacts/ui/FastTrackWindow.java
+++ b/src/com/android/contacts/ui/FastTrackWindow.java
@@ -20,6 +20,7 @@
import com.android.contacts.model.ContactsSource;
import com.android.contacts.model.Sources;
import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.ui.widget.CheckableImageView;
import com.android.contacts.util.Constants;
import com.android.contacts.util.NotifyingAsyncQueryHandler;
import com.android.internal.policy.PolicyManager;
@@ -39,14 +40,12 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.FastTrack;
-import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.Presence;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.SocialContract.Activities;
import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
@@ -133,7 +132,7 @@
private View mFooter;
private View mFooterDisambig;
private ListView mResolveList;
- private CheckableImageView mLastChiclet;
+ private CheckableImageView mLastAction;
/**
* Set of {@link Action} that are associated with the aggregate currently
@@ -159,6 +158,9 @@
private static final int TOKEN_SOCIAL = 2;
private static final int TOKEN_DATA = 3;
+ static final boolean TRACE_LAUNCH = false;
+ static final String TRACE_TAG = "fasttrack";
+
/**
* Prepare a fast-track window to show in the given {@link Context}.
*/
@@ -245,6 +247,10 @@
return;
}
+ if (TRACE_LAUNCH && !android.os.Debug.isMethodTracingActive()) {
+ android.os.Debug.startMethodTracing(TRACE_TAG);
+ }
+
// Prepare header view for requested mode
mMode = mode;
mHeader = getHeaderView(mode);
@@ -330,6 +336,10 @@
mQuerying = false;
mTrack.startAnimation(mTrackAnim);
+
+ if (TRACE_LAUNCH) {
+ android.os.Debug.stopMethodTracing();
+ }
}
/**
@@ -342,7 +352,7 @@
}
boolean hadDecor = mDecor != null;
-
+
if (hadDecor) {
mWindowManager.removeView(mDecor);
mDecor = null;
@@ -350,7 +360,7 @@
}
// Release refrence to last chiclet.
- mLastChiclet = null;
+ mLastAction = null;
// Completely hide header from current mode
mHeader.setVisibility(View.GONE);
@@ -367,7 +377,7 @@
mTrackScroll.fullScroll(View.FOCUS_LEFT);
mWasDownArrow = false;
- setResolveVisible(false);
+ setResolveVisible(false, null);
mQuerying = false;
mHasSummary = false;
@@ -380,7 +390,7 @@
}
mShowing = false;
-
+
// Notify any listeners that we've been dismissed
if (mDismissListener != null) {
mDismissListener.onDismiss(this);
@@ -576,7 +586,6 @@
*/
private static class DataAction implements Action {
private final Context mContext;
- private final ContactsSource mSource;
private final DataKind mKind;
private final String mMimeType;
@@ -589,10 +598,8 @@
/**
* Create an action from common {@link Data} elements.
*/
- public DataAction(Context context, ContactsSource source, String mimeType, DataKind kind,
- Cursor cursor) {
+ public DataAction(Context context, String mimeType, DataKind kind, Cursor cursor) {
mContext = context;
- mSource = source;
mKind = kind;
mMimeType = mimeType;
@@ -656,13 +663,14 @@
/** {@inheritDoc} */
public Drawable getFallbackIcon() {
// Bail early if no valid resources
- if (mSource.resPackageName == null) return null;
+ final String resPackageName = mKind.resPackageName;
+ if (resPackageName == null) return null;
final PackageManager pm = mContext.getPackageManager();
- if (mAlternate && mKind.iconAltRes > 0) {
- return pm.getDrawable(mSource.resPackageName, mKind.iconAltRes, null);
- } else if (mKind.iconRes > 0) {
- return pm.getDrawable(mSource.resPackageName, mKind.iconRes, null);
+ if (mAlternate && mKind.iconAltRes != -1) {
+ return pm.getDrawable(resPackageName, mKind.iconAltRes, null);
+ } else if (mKind.iconRes != -1) {
+ return pm.getDrawable(resPackageName, mKind.iconRes, null);
} else {
return null;
}
@@ -880,22 +888,21 @@
// TODO: find the ContactsSource for this, either from accountType,
// or through lazy-loading when resPackage is set, or default.
- final ContactsSource source = sources.getInflatedSource(accountType,
+ final DataKind kind = sources.getKindOrFallback(accountType, mimeType, mContext,
ContactsSource.LEVEL_MIMETYPES);
- final DataKind kind = source.getKindForMimetype(mimeType);
if (kind != null) {
// Build an action for this data entry, find a mapping to a UI
// element, build its summary from the cursor, and collect it
// along with all others of this MIME-type.
- final Action action = new DataAction(mContext, source, mimeType, kind, cursor);
+ final Action action = new DataAction(mContext, mimeType, kind, cursor);
considerAdd(action, mimeType);
}
// If phone number, also insert as text message action
if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && kind != null) {
- final Action action = new DataAction(mContext, source, Constants.MIME_SMS_ADDRESS,
- kind, cursor);
+ final Action action = new DataAction(mContext, Constants.MIME_SMS_ADDRESS, kind,
+ cursor);
considerAdd(action, Constants.MIME_SMS_ADDRESS);
}
}
@@ -972,10 +979,14 @@
* Helper for showing and hiding {@link #mFooterDisambig}, which will
* correctly manage {@link #mArrowDown} as needed.
*/
- private void setResolveVisible(boolean visible) {
+ private void setResolveVisible(boolean visible, CheckableImageView actionView) {
// Show or hide the resolve list if needed
boolean visibleNow = mFooterDisambig.getVisibility() == View.VISIBLE;
+ if (mLastAction != null) mLastAction.setChecked(false);
+ if (actionView != null) actionView.setChecked(true);
+ mLastAction = actionView;
+
// Bail early if already in desired state
if (visible == visibleNow) return;
@@ -986,21 +997,21 @@
// If showing list, then hide and save state of down arrow
mWasDownArrow = mWasDownArrow || (mArrowDown.getVisibility() == View.VISIBLE);
mArrowDown.setVisibility(View.INVISIBLE);
- mLastChiclet.setChecked(true);
} else {
// If hiding list, restore any down arrow state
mArrowDown.setVisibility(mWasDownArrow ? View.VISIBLE : View.INVISIBLE);
- mLastChiclet.setChecked(false);
}
}
/** {@inheritDoc} */
- public void onClick(View v) {
- mLastChiclet = (CheckableImageView)v;
- final Object tag = v.getTag();
+ public void onClick(View view) {
+ final boolean isActionView = (view instanceof CheckableImageView);
+ final CheckableImageView actionView = isActionView ? (CheckableImageView)view : null;
+
+ final Object tag = view.getTag();
if (tag instanceof Intent) {
// Hide the resolution list, if present
- setResolveVisible(false);
+ setResolveVisible(false, actionView);
this.dismiss();
try {
@@ -1014,7 +1025,7 @@
final ActionList children = (ActionList)tag;
// Show resolution list and set adapter
- setResolveVisible(true);
+ setResolveVisible(true, actionView);
mResolveList.setOnItemClickListener(this);
mResolveList.setAdapter(new BaseAdapter() {
@@ -1063,7 +1074,7 @@
// Back key will first dismiss any expanded resolve list, otherwise
// it will close the entire dialog.
if (mFooterDisambig.getVisibility() == View.VISIBLE) {
- setResolveVisible(false);
+ setResolveVisible(false, null);
} else {
dismiss();
}
@@ -1205,6 +1216,7 @@
Data.MIMETYPE,
Data.IS_PRIMARY,
Data.IS_SUPER_PRIMARY,
+ Data.RAW_CONTACT_ID,
Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5,
Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11,
Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15,
diff --git a/src/com/android/contacts/ui/CheckableImageView.java b/src/com/android/contacts/ui/widget/CheckableImageView.java
similarity index 97%
rename from src/com/android/contacts/ui/CheckableImageView.java
rename to src/com/android/contacts/ui/widget/CheckableImageView.java
index 257e407..ceddf57 100644
--- a/src/com/android/contacts/ui/CheckableImageView.java
+++ b/src/com/android/contacts/ui/widget/CheckableImageView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.contacts.ui;
+package com.android.contacts.ui.widget;
import android.content.Context;
import android.util.AttributeSet;
@@ -49,7 +49,7 @@
public void toggle() {
setChecked(!mChecked);
}
-
+
public boolean isChecked() {
return mChecked;
}
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index dd40634..6bc3005 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -33,6 +33,7 @@
import android.content.ContentProviderOperation;
import android.content.ContentValues;
+import android.content.Context;
import android.content.Entity;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
@@ -67,16 +68,12 @@
mContext = getContext();
}
- /**
- * Build a {@link ContactsSource} that has various odd constraints for
- * testing purposes.
- */
- protected ContactsSource getSource() {
- final ContactsSource list = new ContactsSource();
- list.accountType = TEST_ACCOUNT_TYPE;
- list.setInflatedLevel(ContactsSource.LEVEL_CONSTRAINTS);
+ public static class MockContactsSource extends ContactsSource {
+ @Override
+ protected void inflate(Context context, int inflateLevel) {
+ this.accountType = TEST_ACCOUNT_TYPE;
+ this.setInflatedLevel(ContactsSource.LEVEL_CONSTRAINTS);
- {
// Phone allows maximum 2 home, 1 work, and unlimited other, with
// constraint of 5 numbers maximum.
DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE, -1, -1, 10, true);
@@ -93,10 +90,16 @@
kind.fieldList.add(new EditField(Phone.NUMBER, -1, -1));
kind.fieldList.add(new EditField(Phone.LABEL, -1, -1));
- list.add(kind);
+ addKind(kind);
}
+ }
- return list;
+ /**
+ * Build a {@link ContactsSource} that has various odd constraints for
+ * testing purposes.
+ */
+ protected ContactsSource getSource() {
+ return new MockContactsSource();
}
/**
diff --git a/tests/src/com/android/contacts/StyleManagerTests.java b/tests/src/com/android/contacts/StyleManagerTests.java
deleted file mode 100644
index aaf54be..0000000
--- a/tests/src/com/android/contacts/StyleManagerTests.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2009 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 android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import java.util.Arrays;
-
-import com.android.contacts.StyleManager;
-import com.android.contacts.tests.R;
-
-/**
- * Tests for the StyleManager class.
- */
-@LargeTest
-public class StyleManagerTests extends AndroidTestCase {
-
- public static final String LOG_TAG = "StyleManagerTests";
-
- private StyleManager mStyleManager;
- private static final String PACKAGE_NAME = "com.android.contacts.tests";
- private static final String PHONE_MIMETYPE = "vnd.android.cursor.item/phone";
- private Context mContext;
-
- public StyleManagerTests() {
- super();
- }
-
- @Override
- public void setUp() {
- mContext = getContext();
- mStyleManager = StyleManager.getInstance(mContext);
- }
-
- public void testGetMimetypeIcon() {
- Bitmap phoneIconFromSm = mStyleManager.getMimetypeIcon(mContext, PACKAGE_NAME, PHONE_MIMETYPE);
- int smHeight = phoneIconFromSm.getHeight();
- int smWidth = phoneIconFromSm.getWidth();
-
- Bitmap phoneIconFromRes = BitmapFactory.decodeResource(mContext.getResources(),
- R.drawable.phone_icon, null);
- int resHeight = phoneIconFromRes.getHeight();
- int resWidth = phoneIconFromRes.getWidth();
-
- int[] smPixels = new int[smWidth*smHeight];
- phoneIconFromSm.getPixels(smPixels, 0, smWidth, 0, 0, smWidth, smHeight);
-
- int[] resPixels = new int[resWidth*resHeight];
- phoneIconFromRes.getPixels(resPixels, 0, resWidth, 0, 0, resWidth, resHeight);
-
- assertTrue(Arrays.equals(smPixels, resPixels));
- }
-
- public void testGetMissingMimetypeIcon() {
- Bitmap postalIconFromSm = mStyleManager.getMimetypeIcon(mContext, PACKAGE_NAME,
- "vnd.android.cursor.item/postal-address");
-
- assertNull(postalIconFromSm);
- }
-
- public void testGetDefaultIcon() {
- Bitmap defaultIconFromSm = mStyleManager.getDefaultIcon(mContext, PACKAGE_NAME);
-
- int smHeight = defaultIconFromSm.getHeight();
- int smWidth = defaultIconFromSm.getWidth();
-
- Bitmap defaultIconFromRes = BitmapFactory.decodeResource(mContext.getResources(),
- R.drawable.default_icon, null);
- int resHeight = defaultIconFromRes.getHeight();
- int resWidth = defaultIconFromRes.getWidth();
-
- int[] smPixels = new int[smWidth*smHeight];
- defaultIconFromSm.getPixels(smPixels, 0, smWidth, 0, 0, smWidth, smHeight);
-
- int[] resPixels = new int[resWidth*resHeight];
- defaultIconFromRes.getPixels(resPixels, 0, resWidth, 0, 0, resWidth, resHeight);
-
- assertTrue(Arrays.equals(smPixels, resPixels));
- }
-
- public void testCaching() {
- // Clear cache
- mStyleManager.onPackageChange(PACKAGE_NAME);
- assertTrue(mStyleManager.getIconCacheSize() == 0);
- assertTrue(mStyleManager.getStyleSetCacheSize() == 0);
-
- // Getting the icon should add it to the cache.
- mStyleManager.getDefaultIcon(mContext, PACKAGE_NAME);
- assertTrue(mStyleManager.getIconCacheSize() == 1);
- assertTrue(mStyleManager.getStyleSetCacheSize() == 1);
- assertTrue(mStyleManager.isIconCacheHit(PACKAGE_NAME, StyleManager.DEFAULT_MIMETYPE));
- assertFalse(mStyleManager.isIconCacheHit(PACKAGE_NAME, PHONE_MIMETYPE));
- assertTrue(mStyleManager.isStyleSetCacheHit(PACKAGE_NAME));
-
- mStyleManager.getMimetypeIcon(mContext, PACKAGE_NAME, PHONE_MIMETYPE);
- assertTrue(mStyleManager.getIconCacheSize() == 2);
- assertTrue(mStyleManager.getStyleSetCacheSize() == 1);
- assertTrue(mStyleManager.isIconCacheHit(PACKAGE_NAME, StyleManager.DEFAULT_MIMETYPE));
- assertTrue(mStyleManager.isIconCacheHit(PACKAGE_NAME, PHONE_MIMETYPE));
- assertTrue(mStyleManager.isStyleSetCacheHit(PACKAGE_NAME));
-
- // Clear cache
- mStyleManager.onPackageChange(PACKAGE_NAME);
- assertTrue(mStyleManager.getIconCacheSize() == 0);
- assertTrue(mStyleManager.getStyleSetCacheSize() == 0);
- assertFalse(mStyleManager.isIconCacheHit(PACKAGE_NAME, StyleManager.DEFAULT_MIMETYPE));
- assertFalse(mStyleManager.isIconCacheHit(PACKAGE_NAME, PHONE_MIMETYPE));
- assertFalse(mStyleManager.isStyleSetCacheHit(PACKAGE_NAME));
- }
-
-}