Upstream version 6.34.113.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / core / src / org / xwalk / core / extension / api / contacts / ContactFinder.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.ContentResolver;
8 import android.database.Cursor;
9 import android.os.Build.VERSION;
10 import android.os.Build.VERSION_CODES;
11 import android.provider.ContactsContract.Data;
12 import android.provider.ContactsContract.CommonDataKinds.Email;
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.Note;
18 import android.provider.ContactsContract.CommonDataKinds.Organization;
19 import android.provider.ContactsContract.CommonDataKinds.Phone;
20 import android.provider.ContactsContract.CommonDataKinds.Photo;
21 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
22 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
23 import android.provider.ContactsContract.CommonDataKinds.Website;
24 import android.util.Log;
25 import android.util.Pair;
26
27 import java.util.ArrayList;
28 import java.util.HashSet;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33
34 import org.json.JSONArray;
35 import org.json.JSONException;
36 import org.json.JSONObject;
37
38 /**
39  * This class searches contacts by given options.
40  */
41 public class ContactFinder {
42     private ContactUtils mUtils;
43     private static final String TAG = "ContactFinder";
44
45     public ContactFinder(ContentResolver resolver) {
46         mUtils = new ContactUtils(resolver);
47     }
48
49     public static class FindOption {
50         public String mWhere;
51         public String[] mWhereArgs;
52         public String mSortOrder;
53
54         public FindOption(String where, String[] whereArgs, String sortOrder) {
55             mWhere = where;
56             mWhereArgs = whereArgs;
57             mSortOrder = sortOrder;
58         }
59     }
60
61     private class ContactData {
62         public String lastUpdated;
63         public JSONObject oName;
64         public JSONArray aEmails;
65         public JSONArray aPhotos;
66         public JSONArray aUrls;
67         public JSONArray aCategories;
68         public JSONArray aAddresses;
69         public JSONArray aNumbers;
70         public JSONArray aOrganizations;
71         public JSONArray aJobTitles;
72         public String birthday;
73         public JSONArray aNotes;
74         public JSONArray aImpp;
75         public String anniversary;
76         public String gender;
77         public JSONObject ensurePut(long id) {
78             JSONObject o = new JSONObject();
79             try {
80                 o.put("id", id);
81             } catch (JSONException e) {
82                 Log.e(TAG, "ensurePut - Failed to build json data: " + e.toString());
83             }
84             ensurePut(o, "name", oName);
85             ensurePut(o, "lastUpdated", lastUpdated);
86             ensurePut(o, "emails", aEmails);
87             ensurePut(o, "photos", aPhotos);
88             ensurePut(o, "urls", aUrls);
89             ensurePut(o, "categories", aCategories);
90             ensurePut(o, "addresses", aAddresses);
91             ensurePut(o, "phoneNumbers", aNumbers);
92             ensurePut(o, "organizations", aOrganizations);
93             ensurePut(o, "jobTitles", aJobTitles);
94             ensurePut(o, "birthday", birthday);
95             ensurePut(o, "notes", aNotes);
96             ensurePut(o, "impp", aImpp);
97             ensurePut(o, "anniversary", anniversary);
98             ensurePut(o, "gender", gender);
99             return o;
100         }
101         private <T> void ensurePut(JSONObject o, String jsonName, T t) {
102             try {
103                 if (t != null) o.put(jsonName, t);
104             } catch (JSONException e) {
105                 Log.e(TAG, "ensurePut - Failed to add json data: " + e.toString());
106             }
107         }
108     }
109
110     private JSONObject addString(JSONObject o, Cursor c, String jsonName, String dataName) {
111         try {
112             final String value = c.getString(c.getColumnIndex(dataName));
113             if (o == null) o = new JSONObject();
114             if (value != null) o.put(jsonName, value);
115         } catch (JSONException e) {
116             Log.e(TAG, "addString - Failed to build json data: " + e.toString());
117         }
118         return o;
119     }
120
121     private JSONArray addString(JSONArray array, Cursor c, String dataName) {
122         if (array == null) array = new JSONArray();
123         final String value = c.getString(c.getColumnIndex(dataName));
124         if (value != null) array.put(value);
125         return array;
126     }
127
128     private JSONObject addArrayTop(JSONObject o, Cursor c,
129             String jsonName, String dataName, Map<String, Integer> typeValuesMap) {
130         final String nameString = ContactUtils.getKeyFromValue(
131                 typeValuesMap, Integer.valueOf(c.getString(c.getColumnIndex(dataName))));
132         return ensureAddArrayTop(o, c, jsonName, nameString);
133     }
134
135     private JSONObject addArrayTop(JSONObject o, Cursor c,
136             String jsonName, String dataName) {
137         final String nameString = c.getString(c.getColumnIndex(dataName));
138         return ensureAddArrayTop(o, c, jsonName, nameString);
139     }
140
141     private JSONObject ensureAddArrayTop(JSONObject o, Cursor c,
142             String jsonName, String nameString) {
143         try {
144             if (o == null) o = new JSONObject();
145             if (nameString != null) {
146                 JSONArray nameArray = new JSONArray();
147                 nameArray.put(nameString);
148                 o.put(jsonName, nameArray);
149             }
150         } catch (JSONException e) {
151             Log.e(TAG, "ensureAddArrayTop - Failed to build json data: " + e.toString());
152         }
153         return o;
154     }
155
156     private JSONArray addTypeArray(JSONArray array, Cursor c, String data,
157             Map<String, String> typeMap, Map<String, Integer> typeValuesMap) {
158         try {
159             if (array == null) array = new JSONArray();
160             final String primary = typeMap.get("isSuperPrimary");
161             final String preferred =
162                     (c.getString(c.getColumnIndex(primary)).equals("1")) ? "true" : "false";
163             JSONObject o = new JSONObject();
164             o.put("preferred", preferred);
165             addArrayTop(o, c, "types", typeMap.get("type"), typeValuesMap);
166             String value = c.getString(c.getColumnIndex(data));
167             if (c.getString(c.getColumnIndex(Data.MIMETYPE)).equals(Im.CONTENT_ITEM_TYPE)) {
168                 int protocol = c.getInt(c.getColumnIndex(Im.PROTOCOL));
169                 String prefix = ContactUtils.getKeyFromValue(ContactConstants.imProtocolMap, protocol);
170                 value = prefix + ':' + value;
171             }
172             o.put("value", value);
173             array.put(o);
174         } catch (JSONException e) {
175             Log.e(TAG, "addTypeArray - Failed to build json data: " + e.toString());
176         }
177         return array;
178     }
179
180     private Set<String> getContactIds(FindOption findOption) {
181         Set<String> ids = null;
182         Cursor c = null;
183         try {
184             c = mUtils.mResolver.query(Data.CONTENT_URI, null, findOption.mWhere,
185                                        findOption.mWhereArgs, findOption.mSortOrder);
186             ids = new HashSet<String>();
187             while (c.moveToNext()) {
188                 ids.add(String.valueOf(c.getLong(c.getColumnIndex(Data.CONTACT_ID))));
189             }
190             return ids;
191         } catch (SecurityException e) {
192             Log.e(TAG, "getContactIds: " + e.toString());
193             return null;
194         } finally {
195             if (c != null) c.close();
196         }
197     }
198
199     /**
200      * Generate a string to be used as sort order by SQL clause
201      * @param sortBy e.g. "givenNames", "phoneNumbers"
202      * @param sortOrder e.g. "descending"
203      * @return Data name with SQL clause: "StructuredName.GIVEN_NAME DESC, Phone.NUMBER DESC"
204      */
205     private String getSortOrder(List<String> sortBy, String sortOrder) {
206         if (sortOrder == null) return null;
207
208         String suffix = "";
209         if (sortOrder.equals("ascending")) {
210             suffix = " ASC";
211         } else if (sortOrder.equals("descending")) {
212             suffix = " DESC";
213         }
214
215         String order = "";
216         for (String s : sortBy) {
217             Pair<String, String> fields = ContactConstants.contactDataMap.get(s);
218             if (fields == null) continue;
219             order += fields.first + suffix + ",";
220         }
221         return (order != "") ? order.substring(0, order.length()-1) : null;
222     }
223
224     //TODO(hdq): Currently this function doesn't support multi-column sorting.
225     private JSONArray getContacts(
226             Set<String> contactIds, String sortOrder, String sortByMimeType, Long resultsLimit) {
227         // Get all records of given contactIds.
228         // For example, sort by ascending:
229         // -----------------------------
230         // id  data           mimetype
231         // -----------------------------
232         // 60  +34600000000   phone_v2
233         // 59  +34698765432   phone_v2
234         // 59  David1 Smith   name
235         // 60  David2 Smith   name
236         String where = null;
237         String[] whereArgs = null;
238         if (contactIds.size() != 0) {
239             where = Data.CONTACT_ID + " in (" + ContactUtils.makeQuestionMarkList(contactIds) + ")";
240             whereArgs = contactIds.toArray(new String[contactIds.size()]);
241         }
242         Cursor c = null;
243         Map<Long, ContactData> dataMap = null;
244         try {
245             c = mUtils.mResolver.query(Data.CONTENT_URI, null, where, whereArgs, sortOrder);
246             dataMap = new LinkedHashMap<Long, ContactData>();
247
248             // Read contact IDs to build the array by sortOrder.
249             if (sortOrder != null) {
250                 while (c.moveToNext()) {
251                     // We should only check for mimetype of sorting field.
252                     // As e.g. above, sort by phone number: [60, 59], by name: [59, 60]
253                     String mime = c.getString(c.getColumnIndex(Data.MIMETYPE));
254                     if (!mime.equals(sortByMimeType)) continue;
255                     long id = c.getLong(c.getColumnIndex(Data.CONTACT_ID));
256                     if (!dataMap.containsKey(id)) dataMap.put(id, new ContactData());
257                 }
258                 c.moveToFirst();
259             }
260
261             // Read details of each contacts
262             while (c.moveToNext()) {
263                 long id = c.getLong(c.getColumnIndex(Data.CONTACT_ID));
264                 if (!dataMap.containsKey(id)) dataMap.put(id, new ContactData());
265                 ContactData d = dataMap.get(id);
266                 if (d.lastUpdated == null && VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
267                     d.lastUpdated = mUtils.getLastUpdated(id);
268                 }
269                 String mime = c.getString(c.getColumnIndex(Data.MIMETYPE));
270                 if (mime.equals(StructuredName.CONTENT_ITEM_TYPE)) {
271                     d.oName = addString(d.oName, c, "displayName", StructuredName.DISPLAY_NAME);
272                     d.oName = addArrayTop(d.oName, c, "honorificPrefixes", StructuredName.PREFIX);
273                     d.oName = addArrayTop(d.oName, c, "givenNames", StructuredName.GIVEN_NAME);
274                     d.oName = addArrayTop(d.oName, c, "additionalNames", StructuredName.MIDDLE_NAME);
275                     d.oName = addArrayTop(d.oName, c, "familyNames", StructuredName.FAMILY_NAME);
276                     d.oName = addArrayTop(d.oName, c, "honorificSuffixes", StructuredName.SUFFIX);
277                 } else if (mime.equals(Nickname.CONTENT_ITEM_TYPE)) {
278                     d.oName = addArrayTop(d.oName, c, "nicknames", Nickname.NAME);
279                 } else if (mime.equals(Email.CONTENT_ITEM_TYPE)) {
280                     d.aEmails = addTypeArray(d.aEmails, c, Email.DATA,
281                                              ContactConstants.emailTypeMap,
282                                              ContactConstants.emailTypeValuesMap);
283                 } else if (mime.equals(Photo.CONTENT_ITEM_TYPE)) {
284                     d.aPhotos = addString(d.aPhotos, c, Photo.PHOTO);
285                 } else if (mime.equals(Website.CONTENT_ITEM_TYPE)) {
286                     d.aUrls = addTypeArray(d.aUrls, c, Website.DATA,
287                                            ContactConstants.websiteTypeMap,
288                                            ContactConstants.websiteTypeValuesMap);
289                 } else if (mime.equals(GroupMembership.CONTENT_ITEM_TYPE)) {
290                     String title = mUtils.getGroupTitle(
291                             c.getString(c.getColumnIndex(GroupMembership.GROUP_ROW_ID)));
292                     if (title != null) {
293                         if (d.aCategories == null) d.aCategories = new JSONArray();
294                         d.aCategories.put(title);
295                     }
296                 } else if (mime.equals(StructuredPostal.CONTENT_ITEM_TYPE)) {
297                     d.aAddresses = addTypeArray(d.aAddresses, c, StructuredPostal.DATA,
298                                                 ContactConstants.addressTypeMap,
299                                                 ContactConstants.addressTypeValuesMap);
300                 } else if (mime.equals(Phone.CONTENT_ITEM_TYPE)) {
301                     d.aNumbers = addTypeArray(d.aNumbers, c, Phone.DATA,
302                                               ContactConstants.phoneTypeMap,
303                                               ContactConstants.phoneTypeValuesMap);
304                 } else if (mime.equals(Organization.CONTENT_ITEM_TYPE)) {
305                     d.aOrganizations = addString(d.aOrganizations, c, Organization.COMPANY);
306                 } else if (mime.equals(Organization.CONTENT_ITEM_TYPE)) {
307                     d.aJobTitles = addString(d.aJobTitles, c, Organization.TITLE);
308                 } else if (mime.equals(Event.CONTENT_ITEM_TYPE)) {
309                     int type = Integer.valueOf(c.getString(c.getColumnIndex(Event.TYPE)));
310                     if (type == Event.TYPE_BIRTHDAY) {
311                         d.birthday = c.getString(c.getColumnIndex(Event.START_DATE));
312                     } else if (type == Event.TYPE_ANNIVERSARY) {
313                         d.anniversary = c.getString(c.getColumnIndex(Event.START_DATE));
314                     }
315                 } else if (mime.equals(Note.CONTENT_ITEM_TYPE)) {
316                     d.aNotes = addString(d.aNotes, c, Note.NOTE);
317                 } else if (mime.equals(Im.CONTENT_ITEM_TYPE)) {
318                     d.aImpp = addTypeArray(d.aImpp, c, Im.DATA, ContactConstants.imTypeMap,
319                                            ContactConstants.imTypeValuesMap);
320                 } else if (mime.equals(ContactConstants.CUSTOM_MIMETYPE_GENDER)) {
321                     d.gender = c.getString(c.getColumnIndex(Data.DATA1));
322                 }
323             }
324         } catch (Exception e) {
325             if (e instanceof NumberFormatException || e instanceof SecurityException) {
326                 Log.e(TAG, "getContacts: " + e.toString());
327                 return new JSONArray();
328             } else {
329                 throw new RuntimeException(e);
330             }
331         } finally {
332             if (c != null) c.close();
333         }
334
335         int i = 0;
336         JSONArray returnArray = new JSONArray();
337         for (Map.Entry<Long, ContactData> entry : dataMap.entrySet()) {
338             if (resultsLimit != null && ++i > resultsLimit) break;
339             JSONObject o = entry.getValue().ensurePut(entry.getKey());
340             returnArray.put(o);
341         }
342         return returnArray;
343     }
344
345     private FindOption createFindIDOption(String findString) {
346         ContactJson findJson = new ContactJson(findString);
347         String value = (findString != null) ? findJson.getString("value") : null;
348         if (value == null || value.equals("") || findString.equals("")) {
349             return new FindOption(null, null, null);
350         } else {
351             List<String> args = new ArrayList<String>();
352             List<String> fields = findJson.getStringArray("fields");
353             String operator = findJson.getString("operator");
354             if (operator == null) {
355                 return new FindOption(null, null, null);
356             } else if (operator.equals("is")) {
357                 operator = " = ";
358             } else if (operator.equals("contains")) {
359                 operator = " LIKE ";
360                 value = "%" + value + "%";
361             } else {
362                 Log.e(TAG, "find - Wrong Operator: ["+operator+"], should be 'is' or 'contains'");
363                 return null;
364             }
365             String where = "";
366             for (String field : fields) {
367                 // E.g. for "givenName" should check column of "givenNames".
368                 String column = ContactConstants.findFieldMap.get(field);
369                 // Skip invalid fields
370                 if (column == null) continue;
371                 android.util.Pair<String, String> name = ContactConstants.contactDataMap.get(column);
372                 // E.g. first is GIVEN_NAME, second is StructuredName.MIMETYPE
373                 where += name.first + operator + " ? AND " + Data.MIMETYPE + " = ? or ";
374                 args.add(value);
375                 args.add(name.second);
376             }
377             if (where == "") return new FindOption(null, null, null);
378             // Remove the "or " which appended in the loop above.
379             where = where.substring(0, where.length()-3);
380             String[] whereArgs = args.toArray(new String[args.size()]);
381             return new FindOption(where, whereArgs, null);
382         }
383     }
384
385     public JSONArray find(String findString) {
386         Set<String> ids = getContactIds(createFindIDOption(findString));
387         if (ids == null) return new JSONArray();
388         ContactJson findJson = new ContactJson(findString);
389         List<String> sortBy = findJson.getStringArray("sortBy");
390         String order = getSortOrder(sortBy, findJson.getString("sortOrder"));
391         String orderMimeType = (order == null) ? null :
392                 ContactConstants.contactDataMap.get(sortBy.get(0)).second;
393         String resultsLimit = findJson.getString("resultsLimit");
394         Long resultsLimitLong = (resultsLimit == null) ? null : Long.valueOf(resultsLimit);
395         return getContacts(ids, order, orderMimeType, resultsLimitLong);
396     }
397 }