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