Hack around inexplicable force close from market report.
[acal.git] / src / com / morphoss / acal / contacts / VCardContact.java
blob5886060622d6396214ed6a1cf51344154d5447e5
1 package com.morphoss.acal.contacts;
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.Locale;
7 import java.util.Map;
8 import java.util.Set;
9 import java.util.regex.Matcher;
10 import java.util.regex.Pattern;
12 import android.accounts.Account;
13 import android.content.ContentProviderOperation;
14 import android.content.ContentProviderOperation.Builder;
15 import android.content.ContentUris;
16 import android.content.ContentValues;
17 import android.content.Context;
18 import android.content.OperationApplicationException;
19 import android.database.Cursor;
20 import android.database.DatabaseUtils;
21 import android.net.Uri;
22 import android.os.RemoteException;
23 import android.provider.BaseColumns;
24 import android.provider.ContactsContract;
25 import android.provider.ContactsContract.CommonDataKinds;
26 import android.provider.ContactsContract.RawContacts;
27 import android.provider.ContactsContract.RawContacts.Data;
28 import android.util.Log;
30 import com.morphoss.acal.Constants;
31 import com.morphoss.acal.davacal.AcalCollection;
32 import com.morphoss.acal.davacal.AcalProperty;
33 import com.morphoss.acal.davacal.PropertyName;
34 import com.morphoss.acal.davacal.VCard;
35 import com.morphoss.acal.davacal.VComponent;
36 import com.morphoss.acal.davacal.VComponentCreationException;
37 import com.morphoss.acal.davacal.YouMustSurroundThisMethodInTryCatchOrIllEatYouException;
38 import com.morphoss.acal.providers.DavResources;
40 public class VCardContact {
42 public final static String TAG = "aCal VCardContact";
44 private static final Pattern structuredAddressMatcher = Pattern.compile("^(.*);(.*);(.*);(.*);(.*);(.*);(.*)$");
45 private static final Pattern structuredNameMatcher = Pattern.compile("^(.*);(.*);(.*);(.*);(.*)$");
46 private static final Pattern simpleSplit = Pattern.compile("[.]");
48 private final ContentValues vCardRow;
49 private final VCard sourceCard;
50 private Map<String,Set<AcalProperty>> typeMap = null;
51 private Map<String,Set<AcalProperty>> groupMap = null;
52 private AcalProperty uid = null;
53 private AcalProperty sequence = null;
55 public VCardContact( ContentValues resourceRow, AcalCollection collectionObject ) throws VComponentCreationException {
56 vCardRow = resourceRow;
57 try {
58 sourceCard = (VCard) VComponent.createComponentFromResource(resourceRow, collectionObject);
60 catch ( Exception e ) {
61 Log.w(TAG,"Could not build VCard from resource", e);
62 throw new VComponentCreationException("Could not build VCard from resource", e);
65 try {
66 sourceCard.setPersistentOn();
68 catch ( YouMustSurroundThisMethodInTryCatchOrIllEatYouException e ) { }
69 finally {
70 // We don't sourceCard.setPersistentOff();
71 // We want the contents to be expanded until we're done with this object
74 sequence = sourceCard.getProperty(PropertyName.SEQUENCE);
75 if ( sequence == null )
76 sequence = new AcalProperty("SEQUENCE", "1");
77 uid = sourceCard.getProperty(PropertyName.UID);
78 if ( uid == null ) {
79 uid = new AcalProperty("UID", vCardRow.getAsString(DavResources._ID));
81 buildTypeMap();
85 /**
86 * Traverses the properties, building an index by type and another by association.
88 * VCARD properties may be either like "PROPERTY:VALUE" or possibly as "aname.property:VALUE" (case is irrelevant) and
89 * this is building an index so we can get all "PROPERTY" properties from typeMap and all "aname" properties from groupMap
92 private void buildTypeMap() {
93 typeMap = new HashMap<String,Set<AcalProperty>>();
94 groupMap = new HashMap<String,Set<AcalProperty>>();
96 AcalProperty[] vCardProperties = sourceCard.getAllProperties();
97 String[] nameSplit;
98 Set<AcalProperty> s;
99 for( AcalProperty prop : vCardProperties ) {
100 nameSplit = simpleSplit.split(prop.getName().toUpperCase(Locale.US),2);
101 if ( nameSplit.length == 1 ) {
102 s = typeMap.get(nameSplit[0]);
103 if ( s == null ) {
104 s = new HashSet<AcalProperty>();
105 typeMap.put(nameSplit[0], s);
107 s.add(prop);
109 else {
110 s = typeMap.get(nameSplit[1]);
111 if ( s == null ) {
112 s = new HashSet<AcalProperty>();
113 typeMap.put(nameSplit[1], s);
115 s.add(prop);
117 s = groupMap.get(nameSplit[0]);
118 if ( s == null ) {
119 s = new HashSet<AcalProperty>();
120 groupMap.put(nameSplit[0], s);
122 s.add(prop);
127 public String getUid() {
128 return uid.getValue();
131 public int getSequence() {
132 Integer result = Integer.parseInt(sequence.getValue());
133 if ( result == null ) result = 1;
134 return result;
137 public AcalProperty getProperty(String pName) {
138 return sourceCard.getProperty(pName);
142 public void writeToContact(Context context, Account account, Long androidContactId) {
143 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
144 if ( androidContactId < 0 ) {
145 Log.println(Constants.LOGD,TAG,"Inserting data for '"+this.getProperty("FN").getValue()+"'");
146 ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
147 .withValue(RawContacts.ACCOUNT_TYPE, account.type)
148 .withValue(RawContacts.ACCOUNT_NAME, account.name)
149 .withValue(RawContacts.SYNC1, this.getUid())
150 .build());
152 int rawContactInsertIndex = ops.size();
153 this.writeContactDetails(context, ops, true, rawContactInsertIndex);
155 else {
156 Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, androidContactId);
157 Log.println(Constants.LOGD,TAG,"Updating data for '"+this.getProperty("FN").getValue()+"'");
158 ops.add(ContentProviderOperation.newUpdate(rawContactUri)
159 .withValue(RawContacts.ACCOUNT_TYPE, account.type)
160 .withValue(RawContacts.ACCOUNT_NAME, account.name)
161 .withValue(RawContacts.SYNC1, this.getUid())
162 .withValue(RawContacts.VERSION,this.getSequence())
163 .build());
165 this.writeContactDetails(context, ops, false, androidContactId);
168 try {
169 context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
171 catch (RemoteException e) {
172 // TODO Auto-generated catch block
173 Log.e(TAG,Log.getStackTraceString(e));
175 catch (OperationApplicationException e) {
176 // TODO Auto-generated catch block
177 Log.e(TAG,Log.getStackTraceString(e));
181 private void writeContactDetails(Context context, ArrayList<ContentProviderOperation> ops, boolean isInsert, long rawContactId) {
182 String propertyName;
183 AcalProperty[] vCardProperties = sourceCard.getAllProperties();
184 Uri rawContactUri = (isInsert ?
185 ContactsContract.Data.CONTENT_URI : ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, rawContactId));
186 for (AcalProperty prop : vCardProperties) {
187 Builder op;
188 if ( isInsert ) {
189 op = ContentProviderOperation.newInsert(rawContactUri);
190 op.withValueBackReference(Data.RAW_CONTACT_ID, (int) rawContactId);
192 else {
193 op = ContentProviderOperation.newUpdate(rawContactUri);
195 propertyName = prop.getName();
196 if ( propertyName.equals("FN") ) doStructuredName(context, op, prop, sourceCard.getProperty("N"));
197 else if ( propertyName.equals("TEL") ) doPhone(context, op, prop);
198 else if ( propertyName.equals("ADR") ) doStructuredAddress(context, op, prop);
199 else if ( propertyName.equals("EMAIL")) doEmail(context, op, prop);
200 else
201 continue;
203 ops.add(op.build());
208 private void doEmail(Context context, Builder op, AcalProperty emailProp) {
209 op.withValue(Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE);
210 op.withValue(CommonDataKinds.Email.DATA,emailProp.getValue());
211 String emailType = emailProp.getParam("TYPE").toUpperCase();
212 if ( emailType.contains("HOME") ) {
213 op.withValue(CommonDataKinds.Email.TYPE, CommonDataKinds.Email.TYPE_HOME);
215 else if ( emailType.contains("WORK") ) {
216 op.withValue(CommonDataKinds.Email.TYPE, CommonDataKinds.Email.TYPE_WORK);
218 else {
219 op.withValue(CommonDataKinds.Email.TYPE, CommonDataKinds.Email.TYPE_OTHER);
221 Log.v(TAG,"Processing field EMAIL:"+emailType+":"+emailProp.getValue());
226 private void doStructuredAddress(Context context, Builder op, AcalProperty adrProp) {
227 op.withValue(Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE);
228 String addressType = adrProp.getParam("TYPE").toUpperCase();
230 int opTYpe;
231 if ( addressType.contains("HOME") ) opTYpe = CommonDataKinds.StructuredPostal.TYPE_HOME;
232 else if ( addressType.contains("WORK") ) opTYpe = CommonDataKinds.StructuredPostal.TYPE_WORK;
233 else opTYpe = CommonDataKinds.StructuredPostal.TYPE_OTHER;
234 op.withValue(CommonDataKinds.StructuredPostal.TYPE, opTYpe);
236 Log.v(TAG,"Processing field ADR:"+addressType+":"+adrProp.getValue());
238 Matcher m = structuredAddressMatcher.matcher(adrProp.getValue());
239 if ( m.matches() ) {
241 * The structured type value
242 * corresponds, in sequence, to the post office box; the extended
243 * address (e.g. apartment or suite number); the street address; the
244 * locality (e.g., city); the region (e.g., state or province); the
245 * postal code; the country name.
247 op.withValue(CommonDataKinds.StructuredPostal.POBOX, m.group(1));
248 if ( m.group(2) == null || m.group(2).equals("") )
249 op.withValue(CommonDataKinds.StructuredPostal.STREET, m.group(3));
250 else
251 op.withValue(CommonDataKinds.StructuredPostal.STREET, m.group(2) + " / " + m.group(3));
253 op.withValue(CommonDataKinds.StructuredPostal.CITY, m.group(4));
254 op.withValue(CommonDataKinds.StructuredPostal.REGION, m.group(5));
255 op.withValue(CommonDataKinds.StructuredPostal.POSTCODE, m.group(6));
256 op.withValue(CommonDataKinds.StructuredPostal.COUNTRY, m.group(7));
261 private void doPhone(Context context, Builder op, AcalProperty telProp ) {
262 op.withValue(Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
263 op.withValue(CommonDataKinds.Phone.NUMBER,telProp.getValue());
264 String phoneType = telProp.getParam("TYPE").toUpperCase();
265 if ( phoneType.contains("HOME") ) {
266 op.withValue(CommonDataKinds.Phone.TYPE, CommonDataKinds.Phone.TYPE_HOME);
267 // op.withValue(CommonDataKinds.Phone.LABEL, context.getString(R.string.HomePhone_label));
269 else if ( phoneType.contains("WORK") ) {
270 op.withValue(CommonDataKinds.Phone.TYPE, CommonDataKinds.Phone.TYPE_WORK);
271 // op.withValue(CommonDataKinds.Phone.LABEL, context.getString(R.string.WorkPhone_label));
273 else if ( phoneType.contains("CELL") ) {
274 op.withValue(CommonDataKinds.Phone.TYPE, CommonDataKinds.Phone.TYPE_MOBILE);
275 // op.withValue(CommonDataKinds.Phone.LABEL, context.getString(R.string.CellPhone_label));
277 else {
278 op.withValue(CommonDataKinds.Phone.TYPE, CommonDataKinds.Phone.TYPE_OTHER);
279 // op.withValue(CommonDataKinds.Phone.LABEL, context.getString(R.string.OtherPhone_label));
281 Log.v(TAG,"Processing field TEL:"+phoneType+":"+telProp.getValue());
285 private void doStructuredName(Context context, Builder op, AcalProperty fnProp, AcalProperty nProp) {
286 Log.v(TAG,"Processing field FN:"+fnProp.getValue());
288 op.withValue(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
289 if ( nProp != null ) {
290 Matcher m = structuredNameMatcher.matcher(nProp.getValue());
291 if ( m.matches() ) {
293 * The structured property value corresponds, in
294 * sequence, to the Surname (also known as family name), Given Names,
295 * Honorific Prefixes, and Honorific Suffixes.
297 op.withValue(CommonDataKinds.StructuredName.FAMILY_NAME, m.group(1));
298 op.withValue(CommonDataKinds.StructuredName.GIVEN_NAME, m.group(2));
299 op.withValue(CommonDataKinds.StructuredName.PREFIX, m.group(3));
300 op.withValue(CommonDataKinds.StructuredName.SUFFIX, m.group(4));
301 Log.v(TAG,"Processing 'N' field: '"+nProp.getValue()+"' prefix>"
302 + m.group(3) + "< firstname> " + m.group(2) + "< lastname>" + m.group(1) + "< suffix>" + m.group(4));
306 op.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, fnProp.getValue());
310 public static ContentValues getAndroidContact(Context context, Long rawContactId) {
311 Uri contactDataUri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, rawContactId);
312 Cursor cur = context.getContentResolver().query(contactDataUri, null,
313 BaseColumns._ID+"=?", new String[] { ""+rawContactId }, null);
314 if ( cur.moveToFirst() ) {
315 ContentValues result = new ContentValues();
316 DatabaseUtils.cursorRowToContentValues(cur, result);
317 return result;
319 return null;
323 public void writeToVCard(Context context, ContentValues androidContact) {
324 sourceCard.setEditable();
326 Log.println( Constants.LOGD, TAG, "I should write this to a VCard!" );
327 for( Map.Entry<String,Object> androidValue : androidContact.valueSet() ) {
328 String key = androidValue.getKey();
329 Object value = androidValue.getValue();
330 Log.println( Constants.LOGD, TAG, key+"="+(value == null ? "null" : value.toString()) );