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
;
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
;
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
);
66 sourceCard
.setPersistentOn();
68 catch ( YouMustSurroundThisMethodInTryCatchOrIllEatYouException e
) { }
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
);
79 uid
= new AcalProperty("UID", vCardRow
.getAsString(DavResources
._ID
));
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();
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]);
104 s
= new HashSet
<AcalProperty
>();
105 typeMap
.put(nameSplit
[0], s
);
110 s
= typeMap
.get(nameSplit
[1]);
112 s
= new HashSet
<AcalProperty
>();
113 typeMap
.put(nameSplit
[1], s
);
117 s
= groupMap
.get(nameSplit
[0]);
119 s
= new HashSet
<AcalProperty
>();
120 groupMap
.put(nameSplit
[0], s
);
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;
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())
152 int rawContactInsertIndex
= ops
.size();
153 this.writeContactDetails(context
, ops
, true, rawContactInsertIndex
);
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())
165 this.writeContactDetails(context
, ops
, false, androidContactId
);
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
) {
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
) {
189 op
= ContentProviderOperation
.newInsert(rawContactUri
);
190 op
.withValueBackReference(Data
.RAW_CONTACT_ID
, (int) rawContactId
);
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
);
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
);
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();
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());
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));
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));
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());
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
);
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()) );