Split sources, partial inflation, third-party support.
This change splits HardCodedSources into separate classes
so they could borrow helpers from fallback. This also
finishes up FallbackSource so it handles all new types
supported by ContactsContract. The view inflaters now
handle showing all types, even if not supported as a choice
during edit. This approach also allows us to partially
inflate sources, speeding up view and FastTrack. Fixes
http://b/2116999 and http://b/2126675 and makes progress
towards fixing http://b/2134623
This change also fixes on-phone contacts, meaning we
always have fallback sources, fixing http://b/2119637 and
http://b/2123401
Repurpose code from StyleManager for Sources inflation of
third-party data sources, fixing http://b/2126691
Fix FastTrack chicklet bug so we uncheck when switching
between tabs. Since all types are in framework, we borrow
those strings here, and also clean up our descriptions for
translation.
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 1b14aad..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;
@@ -1945,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;
@@ -1969,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;
}
@@ -2013,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
@@ -2371,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
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/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..0b07a57 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.
@@ -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 be0e185..bd68290 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();
+ }
}
/**
@@ -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;
@@ -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,7 +979,7 @@
* 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;
@@ -986,24 +993,25 @@
// 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);
}
+
+ if (mLastAction != null) mLastAction.setChecked(!visible);
+ if (actionView != null) actionView.setChecked(visible);
+ mLastAction = actionView;
}
/** {@inheritDoc} */
- public void onClick(View v) {
- if (v instanceof CheckableImageView) {
- mLastChiclet = (CheckableImageView)v;
- }
+ public void onClick(View view) {
+ final boolean isActionView = (view instanceof CheckableImageView);
+ final CheckableImageView actionView = isActionView ? (CheckableImageView)view : null;
- final Object tag = v.getTag();
+ final Object tag = view.getTag();
if (tag instanceof Intent) {
// Hide the resolution list, if present
- setResolveVisible(false);
+ setResolveVisible(false, actionView);
this.dismiss();
try {
@@ -1017,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() {
@@ -1066,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();
}
@@ -1208,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;
}