Upstream version 5.34.97.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / core / src / org / xwalk / core / extension / api / contacts / ContactSaver.java
1 // Copyright (c) 2013 Intel Corporation. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.xwalk.core.extension.api.contacts;
6
7 import android.content.ContentProviderOperation;
8 import android.content.ContentProviderOperation.Builder;
9 import android.content.ContentResolver;
10 import android.content.OperationApplicationException;
11 import android.os.RemoteException;
12 import android.provider.ContactsContract;
13 import android.provider.ContactsContract.CommonDataKinds.Event;
14 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
15 import android.provider.ContactsContract.CommonDataKinds.Im;
16 import android.provider.ContactsContract.CommonDataKinds.Nickname;
17 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
18 import android.provider.ContactsContract.Data;
19 import android.util.Log;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.json.JSONArray;
28 import org.json.JSONException;
29 import org.json.JSONObject;
30
31 import org.xwalk.core.extension.api.contacts.ContactConstants.ContactMap;
32
33 /**
34  * This class saves contacts according to a given JSONString.
35  */
36 public class ContactSaver {
37     private ContactUtils mUtils;
38     private static final String TAG = "ContactSaver";
39
40     private JSONObject mContact;
41     private ContactJson mJson;
42     private String mId;
43     private boolean mIsUpdate;
44     private ArrayList<ContentProviderOperation> mOps;
45
46     public ContactSaver(ContentResolver resolver) {
47         mUtils = new ContactUtils(resolver);
48     }
49
50     // Update a contact
51     private Builder newUpdateBuilder(String mimeType) {
52         Builder builder = ContentProviderOperation.newUpdate(Data.CONTENT_URI);
53         builder.withSelection(
54                 Data.CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
55                 new String[]{mId, mimeType});
56         return builder;
57     }
58
59     // Add a new contact
60     private Builder newInsertBuilder(String mimeType) {
61         Builder builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
62         builder.withValueBackReference(Data.RAW_CONTACT_ID, 0);
63         builder.withValue(Data.MIMETYPE, mimeType);
64         return builder;
65     }
66
67     // Add a new field to an existing contact
68     private Builder newInsertFieldBuilder(String mimeType) {
69         String rawId = mUtils.getRawId(mId);
70         if (rawId == null) {
71             Log.e(TAG, "Failed to create builder to insert field of " + mId);
72             return null;
73         }
74         Builder builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
75         builder.withValue(Data.RAW_CONTACT_ID, mUtils.getRawId(mId));
76         builder.withValue(Data.MIMETYPE, mimeType);
77         return builder;
78     }
79
80     // Add a new contact or add a new field to an existing contact
81     private Builder newInsertContactOrFieldBuilder(String mimeType) {
82         return mIsUpdate ? newInsertFieldBuilder(mimeType)
83                          : newInsertBuilder(mimeType);
84     }
85
86     // Add a new contact or update a contact
87     private Builder newBuilder(String mimeType) {
88         return mIsUpdate ? newUpdateBuilder(mimeType)
89                          : newInsertBuilder(mimeType);
90     }
91
92     // Build by a data array with types
93     private void buildByArray(ContactMap contactMap) {
94         if (!mContact.has(contactMap.mName)) {
95             return;
96         }
97
98         // When updating multiple records of one MIMEType,
99         // we need to flush the old records and then insert new ones later.
100         //
101         // For example, it is possible that a contact has several phone numbers,
102         // in data table it will be like this:
103         // CONTACT_ID  MIMETYPE  TYPE  DATA1
104         // ------------------------------------------
105         //        374  Phone_v2  Work  +4412345678
106         //        374  Phone_v2  Work  +4402778877
107         // In this case if we update by SQL selection clause directly,
108         // will get two same records of last update value.
109         //
110         if (mIsUpdate) {
111             mUtils.cleanByMimeType(mId, contactMap.mMimeType);
112         }
113         try {
114             final JSONArray fields = mContact.getJSONArray(contactMap.mName);
115             for (int i = 0; i < fields.length(); ++i) {
116                 ContactJson json = new ContactJson(fields.getJSONObject(i));
117                 List<String> typeList = json.getStringArray("types");
118                 if (typeList != null && !typeList.isEmpty()) {
119                     // Currently we can't store multiple types in Android
120                     final String type = typeList.get(0);
121                     final Integer iType = contactMap.mTypeValueMap.get(type);
122
123                     Builder builder = newInsertContactOrFieldBuilder(contactMap.mMimeType);
124                     if (builder == null) return;
125
126                     if (json.getBoolean("preferred")) {
127                         builder.withValue(contactMap.mTypeMap.get("isPrimary"), 1);
128                         builder.withValue(contactMap.mTypeMap.get("isSuperPrimary"), 1);
129                     }
130                     if (iType != null) {
131                         builder.withValue(contactMap.mTypeMap.get("type"), iType);
132                     }
133                     for (Map.Entry<String, String> entry : contactMap.mDataMap.entrySet()) {
134                         String value = json.getString(entry.getValue());
135                         if (contactMap.mName.equals("impp")) {
136                             int colonIdx = value.indexOf(':');
137                             // An impp must indicate its protocol type by ':'
138                             if (-1 == colonIdx) continue;
139                             String protocol = value.substring(0, colonIdx);
140                             builder.withValue(Im.PROTOCOL,
141                                     ContactConstants.imProtocolMap.get(protocol));
142                             value = value.substring(colonIdx+1);
143                         }
144                         builder.withValue(entry.getKey(), value);
145                     }
146                     mOps.add(builder.build());
147                 }
148             }
149         } catch (JSONException e) {
150             Log.e(TAG, "Failed to parse json data of " + contactMap.mName + ": " + e.toString());
151         }
152     }
153
154     // Build by a data array without types
155     private void buildByArray(String mimeType, String data, List<String> dataEntries) {
156         if (mIsUpdate) {
157             mUtils.cleanByMimeType(mId, mimeType);
158         }
159         for (String entry : dataEntries) {
160             Builder builder = newInsertContactOrFieldBuilder(mimeType);
161             if (builder == null) return;
162             builder.withValue(data, entry);
163             mOps.add(builder.build());
164         }
165     }
166
167     private void buildByArray(ContactMap contactMap, String data, List<String> dataEntries) {
168         if (mContact.has(contactMap.mName)) {
169             buildByArray(contactMap.mMimeType, data, dataEntries);
170         }
171     }
172
173     private void buildByDate(String name, String mimeType, String data) {
174         buildByDate(name, mimeType, data, null, 0);
175     }
176
177     private void buildByDate(String name, String mimeType, String data, String type, int dateType) {
178         if (!mContact.has(name)) return;
179
180         final String fullDateString = mJson.getString(name);
181         final String dateString = mUtils.dateTrim(fullDateString);
182         Builder builder = newBuilder(mimeType);
183         builder.withValue(data, dateString);
184         if (type != null) builder.withValue(type, dateType);
185         mOps.add(builder.build());
186     }
187
188     private void buildByEvent(String eventName, int eventType) {
189         buildByDate(eventName, Event.CONTENT_ITEM_TYPE, Event.START_DATE, Event.TYPE, eventType);
190     }
191
192     private void buildByContactMapList() {
193         for (ContactMap contactMap : ContactConstants.contactMapList) {
194             if (contactMap.mTypeMap != null) { // Field that has type.
195                 buildByArray(contactMap);
196             } else { // Field that contains no type.
197                 buildByArray(contactMap, contactMap.mDataMap.get("data"),
198                              mJson.getStringArray(contactMap.mName));
199             }
200         }
201     }
202
203     private void PutToContact(String id) {
204         if (id == null) return;
205         try {
206             mContact.put("id", id);
207         } catch (JSONException e) {
208             Log.e(TAG, "Failed to put id " + id + " into contact" + e.toString());
209         }
210     }
211
212     public JSONObject save(String saveString) {
213         mOps = new ArrayList<ContentProviderOperation>();
214         try {
215             mContact = new JSONObject(saveString);
216         } catch (JSONException e) {
217             Log.e(TAG, "Failed to parse json data: " + e.toString());
218             return new JSONObject();
219         }
220
221         mJson = new ContactJson(mContact);
222
223         Builder builder = null;
224         mId = mJson.getString("id");
225         mIsUpdate = mUtils.hasID(mId);
226
227         Set<String> oldRawIds = null;
228         if (!mIsUpdate) { // Create a null record for inserting later
229             oldRawIds = mUtils.getCurrentRawIds();
230             mId = null;
231             builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
232             builder.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null);
233             builder.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
234             mOps.add(builder.build());
235         }
236
237         // W3C                  Android
238         //-------------------------------------------------
239         // displayName          StructuredName.display_name
240         // honorificPrefixes    StructuredName.prefix
241         // givenNames           StructuredName.given_name
242         // additionalNames      StructuredName.middle_name
243         // familyNames          StructuredName.family_name
244         // honorificSuffixes    StructuredName.suffix
245         // nicknames            Nickname.name
246         if (mContact.has("name")) {
247             final JSONObject name = mJson.getObject("name");
248             final ContactJson nameJson = new ContactJson(name);
249             builder = newBuilder(StructuredName.CONTENT_ITEM_TYPE);
250             builder.withValue(StructuredName.DISPLAY_NAME, nameJson.getString("displayName"));
251             //FIXME(hdq): should read all names
252             builder.withValue(StructuredName.FAMILY_NAME, nameJson.getFirstValue("familyNames"));
253             builder.withValue(StructuredName.GIVEN_NAME, nameJson.getFirstValue("givenNames"));
254             builder.withValue(StructuredName.MIDDLE_NAME, nameJson.getFirstValue("additionalNames"));
255             builder.withValue(StructuredName.PREFIX, nameJson.getFirstValue("honorificPrefixes"));
256             builder.withValue(StructuredName.SUFFIX, nameJson.getFirstValue("honorificSuffixes"));
257             mOps.add(builder.build());
258
259             // Nickname belongs to another mimetype, so we need another builder for it.
260             if (name.has("nicknames")) {
261                 builder = newBuilder(Nickname.CONTENT_ITEM_TYPE);
262                 builder.withValue(Nickname.NAME, nameJson.getFirstValue("nicknames"));
263                 mOps.add(builder.build());
264             }
265         }
266
267         if (mContact.has("categories")) {
268             List<String> groupIds = new ArrayList<String>();
269             for (String groupTitle : mJson.getStringArray("categories")) {
270                 groupIds.add(mUtils.getEnsuredGroupId(groupTitle));
271             }
272             buildByArray(GroupMembership.CONTENT_ITEM_TYPE, GroupMembership.GROUP_ROW_ID, groupIds);
273         }
274
275         if (mContact.has("gender")) {
276             final String gender = mJson.getString("gender");
277             if (Arrays.asList("male", "female", "other", "none", "unknown").contains(gender)) {
278                 builder = newBuilder(ContactConstants.CUSTOM_MIMETYPE_GENDER);
279                 builder.withValue(Data.DATA1, gender);
280                 mOps.add(builder.build());
281             }
282         }
283
284         buildByDate("lastUpdated", ContactConstants.CUSTOM_MIMETYPE_LASTUPDATED, Data.DATA1);
285         buildByEvent("birthday", Event.TYPE_BIRTHDAY);
286         buildByEvent("anniversary", Event.TYPE_ANNIVERSARY);
287
288         buildByContactMapList();
289
290         // Perform the operation batch
291         try {
292             mUtils.mResolver.applyBatch(ContactsContract.AUTHORITY, mOps);
293         } catch (Exception e) {
294             if (e instanceof RemoteException ||
295                 e instanceof OperationApplicationException ||
296                 e instanceof SecurityException) {
297                 Log.e(TAG, "Failed to apply batch: " + e.toString());
298                 return new JSONObject();
299             } else {
300                 throw new RuntimeException(e);
301             }
302         }
303
304         // If it is a new contact, need to get and return its auto-generated id.
305         if (!mIsUpdate) {
306             Set<String> newRawIds = mUtils.getCurrentRawIds();
307             if (newRawIds == null) return new JSONObject();
308             newRawIds.removeAll(oldRawIds);
309             if (newRawIds.size() != 1) {
310                 Log.e(TAG, "Something wrong after batch applied, "
311                         + "new raw ids are: " + newRawIds.toString());
312                 return mContact;
313             }
314             String id = mUtils.getId(newRawIds.iterator().next());
315             PutToContact(id);
316         }
317         return mContact;
318     }
319 }