1 /*
2  * Copyright (c) 2015, Motorola Mobility LLC
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     - Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     - Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     - Neither the name of Motorola Mobility nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY LLC BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26  * DAMAGE.
27  */
28 
29 package com.android.service.ims.presence;
30 
31 import java.util.ArrayList;
32 import java.util.LinkedHashMap;
33 import java.util.List;
34 
35 import android.content.ContentProviderOperation;
36 import android.content.ContentResolver;
37 import android.content.ContentValues;
38 import android.content.Context;
39 import android.content.OperationApplicationException;
40 import android.database.Cursor;
41 import android.database.sqlite.SQLiteException;
42 import android.os.RemoteException;
43 import android.provider.ContactsContract;
44 import android.provider.ContactsContract.Contacts;
45 import android.provider.ContactsContract.Data;
46 import android.provider.ContactsContract.CommonDataKinds.Phone;
47 import android.text.TextUtils;
48 
49 import com.android.ims.internal.ContactNumberUtils;
50 import com.android.ims.internal.EABContract;
51 import com.android.ims.internal.Logger;
52 
53 public class EABDbUtil {
54     static private Logger logger = Logger.getLogger("EABDbUtil");
55     public static final String ACCOUNT_TYPE = "com.android.rcs.eab.account";
56 
validateAndSyncFromContactsDb(Context context)57     public static boolean validateAndSyncFromContactsDb(Context context) {
58         logger.debug("Enter validateAndSyncFromContactsDb");
59         boolean response = true;
60         // Get the last stored contact changed timestamp and sync only delta contacts.
61         long contactLastChange = SharedPrefUtil.getLastContactChangedTimestamp(context, 0);
62         logger.debug("contact last updated time before init :" + contactLastChange);
63         ContentResolver contentResolver = context.getContentResolver();
64         String[] projection = new String[] {
65                 ContactsContract.Contacts._ID,
66                 ContactsContract.Contacts.HAS_PHONE_NUMBER,
67                 ContactsContract.Contacts.DISPLAY_NAME,
68                 ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP };
69         String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + "> '0' AND "
70                 + ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
71                 + " >'" + contactLastChange + "'";
72         String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " asc";
73         Cursor cursor = null;
74         try {
75             cursor = contentResolver.query(Contacts.CONTENT_URI, projection, selection,
76                     null, sortOrder);
77         } catch (Exception e) {
78             logger.error("validateAndSyncFromContactsDb() cursor exception:", e);
79         }
80         ArrayList<PresenceContact> allEligibleContacts = new ArrayList<PresenceContact>();
81 
82         if (cursor != null) {
83             logger.debug("cursor count : " + cursor.getCount());
84         } else {
85             logger.debug("cursor = null");
86         }
87 
88         if (cursor != null && cursor.moveToFirst()) {
89             do {
90                 String id = cursor.getString(cursor.getColumnIndex(Contacts._ID));
91                 Long time = cursor.getLong(cursor.getColumnIndex(
92                         Contacts.CONTACT_LAST_UPDATED_TIMESTAMP));
93                 // Update the latest contact last modified timestamp.
94                 if (contactLastChange < time) {
95                     contactLastChange = time;
96                 }
97                 String[] commonDataKindsProjection = new String[] {
98                         ContactsContract.CommonDataKinds.Phone._ID,
99                         ContactsContract.CommonDataKinds.Phone.NUMBER,
100                         ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
101                         ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID,
102                         ContactsContract.CommonDataKinds.Phone.CONTACT_ID };
103                 Cursor pCur = null;
104                 try {
105                     pCur = contentResolver.query(
106                             ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
107                             commonDataKindsProjection,
108                             ContactsContract.CommonDataKinds.Phone.CONTACT_ID
109                                 + " = ?", new String[] { id }, null);
110                 } catch (Exception e) {
111                     logger.error("validateAndSyncFromContactsDb() pCur exception:", e);
112                 }
113                 // ArrayList to avoid duplicate entries of contactNumber having same
114                 // contactId, rawContactId and dataId.
115                 ArrayList<String> phoneNumList = new ArrayList<String>();
116 
117                 if (pCur != null && pCur.moveToFirst()) {
118                     do {
119                         String contactNumber = pCur.getString(pCur.getColumnIndex(
120                                 ContactsContract.CommonDataKinds.Phone.NUMBER));
121                         if (validateEligibleContact(context, contactNumber)) {
122                             String contactName = pCur.getString(pCur.getColumnIndex(
123                                     ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
124                             String rawContactId = pCur.getString(pCur.getColumnIndex(
125                                     ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID));
126                             String contactId = pCur.getString(pCur.getColumnIndex(
127                                     ContactsContract.CommonDataKinds.Phone.CONTACT_ID));
128                             String dataId = pCur.getString(pCur.getColumnIndex(
129                                     ContactsContract.CommonDataKinds.Phone._ID));
130                             String formattedNumber = formatNumber(contactNumber);
131                             String uniquePhoneNum = formattedNumber + contactId
132                                     + rawContactId + dataId;
133                             logger.debug("uniquePhoneNum : " + uniquePhoneNum);
134                             if (phoneNumList.contains(uniquePhoneNum)) continue;
135                             phoneNumList.add(uniquePhoneNum);
136 
137                             allEligibleContacts.add(new PresenceContact(contactName, contactNumber,
138                                     formattedNumber, rawContactId, contactId, dataId));
139                             logger.debug("Eligible List Name: " + contactName +
140                                     " Number:" + contactNumber + " RawContactID: " + rawContactId +
141                                     " contactId: " + contactId + " Data.ID : " + dataId
142                                     + " formattedNumber: " + formattedNumber);
143                         }
144                     } while (pCur.moveToNext());
145                 }
146                 if (pCur != null) {
147                     pCur.close();
148                 }
149             } while (cursor.moveToNext());
150         }
151         if (null != cursor) {
152             cursor.close();
153         }
154         if (allEligibleContacts.size() > 0) {
155             logger.debug("Adding : " + allEligibleContacts.size() +
156                     " new contact numbers to EAB db.");
157             addContactsToEabDb(context, allEligibleContacts);
158             logger.debug("contact last updated time after init :" + contactLastChange);
159             SharedPrefUtil.saveLastContactChangedTimestamp(context, contactLastChange);
160             SharedPrefUtil.saveLastContactDeletedTimestamp(context, contactLastChange);
161         }
162         logger.debug("Exit validateAndSyncFromContactsDb contact numbers synced : " +
163                 allEligibleContacts.size());
164         return response;
165     }
166 
addContactsToEabDb(Context context, ArrayList<PresenceContact> contactList)167     public static void addContactsToEabDb(Context context,
168             ArrayList<PresenceContact> contactList) {
169         ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>();
170 
171         logger.debug("Adding Contacts to EAB DB");
172         // To avoid the following exception - Too many content provider operations
173         // between yield points. The maximum number of operations per yield point is
174         // 500 for exceuteDB()
175         int yieldPoint = 300;
176         for (int j = 0; j < contactList.size(); j++) {
177             addContactToEabDb(context, operation, contactList.get(j).getDisplayName(),
178                     contactList.get(j).getPhoneNumber(), contactList.get(j).getFormattedNumber(),
179                     contactList.get(j).getRawContactId(), contactList.get(j).getContactId(),
180                     contactList.get(j).getDataId());
181             if (yieldPoint == j) {
182                 exceuteDB(context, operation);
183                 operation = null;
184                 operation = new ArrayList<ContentProviderOperation>();
185                 yieldPoint += 300;
186             }
187         }
188         exceuteDB(context, operation);
189     }
190 
addContactToEabDb( Context context, ArrayList<ContentProviderOperation> ops, String displayName, String phoneNumber, String formattedNumber, String rawContactId, String contactId, String dataId)191     private static void addContactToEabDb(
192             Context context, ArrayList<ContentProviderOperation> ops, String displayName,
193             String phoneNumber, String formattedNumber, String rawContactId, String contactId,
194             String dataId) {
195         ops.add(ContentProviderOperation
196                 .newInsert(EABContract.EABColumns.CONTENT_URI)
197                 .withValue(EABContract.EABColumns.CONTACT_NAME, displayName)
198                 .withValue(EABContract.EABColumns.CONTACT_NUMBER, phoneNumber)
199                 .withValue(EABContract.EABColumns.FORMATTED_NUMBER, formattedNumber)
200                 .withValue(EABContract.EABColumns.ACCOUNT_TYPE, ACCOUNT_TYPE)
201                 .withValue(EABContract.EABColumns.RAW_CONTACT_ID, rawContactId)
202                 .withValue(EABContract.EABColumns.CONTACT_ID, contactId)
203                 .withValue(EABContract.EABColumns.DATA_ID, dataId).build());
204     }
205 
deleteContactsFromEabDb(Context context, ArrayList<PresenceContact> contactList)206     public static void deleteContactsFromEabDb(Context context,
207             ArrayList<PresenceContact> contactList) {
208         ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>();
209 
210         logger.debug("Deleting Contacts from EAB DB");
211         String[] contactIdList = new String[contactList.size()];
212         // To avoid the following exception - Too many content provider operations
213         // between yield points. The maximum number of operations per yield point is
214         // 500 for exceuteDB()
215         int yieldPoint = 300;
216         for (int j = 0; j < contactList.size(); j++) {
217             contactIdList[j] = contactList.get(j).getContactId().toString();
218             deleteContactFromEabDb(context, operation, contactIdList[j]);
219             if (yieldPoint == j) {
220                 exceuteDB(context, operation);
221                 operation = null;
222                 operation = new ArrayList<ContentProviderOperation>();
223                 yieldPoint += 300;
224             }
225         }
226         exceuteDB(context, operation);
227     }
228 
deleteContactFromEabDb(Context context, ArrayList<ContentProviderOperation> ops, String contactId)229     private static void deleteContactFromEabDb(Context context,
230             ArrayList<ContentProviderOperation> ops, String contactId) {
231         // Add operation only if there is an entry in EABProvider table.
232         String[] eabProjection = new String[] {
233                 EABContract.EABColumns.CONTACT_NUMBER,
234                 EABContract.EABColumns.CONTACT_ID };
235         String eabWhereClause = null;
236         if (ContactsContract.Profile.MIN_ID == Long.valueOf(contactId)) {
237             eabWhereClause = EABContract.EABColumns.CONTACT_ID + " >='" + contactId + "'";
238         } else {
239             eabWhereClause = EABContract.EABColumns.CONTACT_ID + " ='" + contactId + "'";
240         }
241         Cursor eabDeleteCursor = context.getContentResolver().query(
242                 EABContract.EABColumns.CONTENT_URI, eabProjection,
243                 eabWhereClause, null, null);
244         if (null != eabDeleteCursor) {
245             int count = eabDeleteCursor.getCount();
246             logger.debug("cursor count : " + count);
247             if (count > 0) {
248                 eabDeleteCursor.moveToNext();
249                 long eabDeleteContactId = eabDeleteCursor.
250                         getLong(eabDeleteCursor.getColumnIndex(EABContract.EABColumns.CONTACT_ID));
251                 logger.debug("eabDeleteContactId : " + eabDeleteContactId);
252                 if (ContactsContract.Profile.MIN_ID == Long.valueOf(contactId)) {
253                     if (ContactsContract.isProfileId(eabDeleteContactId)) {
254                         logger.debug("Deleting Profile contact.");
255                         ops.add(ContentProviderOperation
256                             .newDelete(EABContract.EABColumns.CONTENT_URI)
257                             .withSelection(EABContract.EABColumns.CONTACT_ID + " >= ?",
258                                     new String[] { contactId }).build());
259                     } else {
260                         logger.debug("Not a Profile contact. Do nothing.");
261                     }
262                 } else {
263                     ops.add(ContentProviderOperation
264                         .newDelete(EABContract.EABColumns.CONTENT_URI)
265                         .withSelection(EABContract.EABColumns.CONTACT_ID + " = ?",
266                                 new String[] { contactId }).build());
267                 }
268             }
269             eabDeleteCursor.close();
270         }
271 
272     }
273 
deleteNumbersFromEabDb(Context context, ArrayList<PresenceContact> contactList)274     public static void deleteNumbersFromEabDb(Context context,
275             ArrayList<PresenceContact> contactList) {
276         ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>();
277 
278         logger.debug("Deleting Number from EAB DB");
279         String[] rawContactIdList = new String [contactList.size()];
280         String[] DataIdList = new String [contactList.size()];
281         // To avoid the following exception - Too many content provider operations
282         // between yield points. The maximum number of operations per yield point is
283         // 500 for exceuteDB()
284         int yieldPoint = 300;
285         for (int j = 0; j < contactList.size(); j++) {
286             rawContactIdList[j] = contactList.get(j).getRawContactId();
287             DataIdList[j] = contactList.get(j).getDataId();
288             deleteNumberFromEabDb(context, operation, rawContactIdList[j], DataIdList[j]);
289             if (yieldPoint == j) {
290                 exceuteDB(context, operation);
291                 operation = null;
292                 operation = new ArrayList<ContentProviderOperation>();
293                 yieldPoint += 300;
294             }
295         }
296         exceuteDB(context, operation);
297     }
298 
deleteNumberFromEabDb(Context context, ArrayList<ContentProviderOperation> ops, String rawContactId, String dataId)299     private static void deleteNumberFromEabDb(Context context,
300             ArrayList<ContentProviderOperation> ops, String rawContactId, String dataId) {
301         // Add operation only if there is an entry in EABProvider table.
302         String[] eabProjection = new String[] {
303                 EABContract.EABColumns.CONTACT_NUMBER };
304         String eabWhereClause = EABContract.EABColumns.RAW_CONTACT_ID + " ='" + rawContactId
305                 + "' AND " + EABContract.EABColumns.DATA_ID + " ='" + dataId + "'";
306         Cursor eabDeleteCursor = context.getContentResolver().query(
307                 EABContract.EABColumns.CONTENT_URI, eabProjection,
308                 eabWhereClause, null, null);
309         if (null != eabDeleteCursor) {
310             int count = eabDeleteCursor.getCount();
311             logger.debug("Delete number cursor count : " + count);
312             if (count > 0) {
313                 ops.add(ContentProviderOperation.newDelete(EABContract.EABColumns.CONTENT_URI)
314                         .withSelection(EABContract.EABColumns.RAW_CONTACT_ID + " = ? AND "
315                                         + EABContract.EABColumns.DATA_ID + " = ?",
316                                 new String[]{rawContactId, dataId}).build());
317             }
318             eabDeleteCursor.close();
319         }
320     }
321 
updateNamesInEabDb(Context context, ArrayList<PresenceContact> contactList)322     public static void updateNamesInEabDb(Context context,
323             ArrayList<PresenceContact> contactList) {
324         ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>();
325 
326         logger.debug("Update name in EAB DB");
327         String[] phoneNameList = new String[contactList.size()];
328         String[] phoneNumberList = new String[contactList.size()];
329         String[] rawContactIdList = new String [contactList.size()];
330         String[] dataIdList = new String [contactList.size()];
331         // To avoid the following exception - Too many content provider operations
332         // between yield points. The maximum number of operations per yield point is
333         // 500 for exceuteDB()
334         int yieldPoint = 300;
335         for (int j = 0; j < contactList.size(); j++) {
336             phoneNameList[j] = contactList.get(j).getDisplayName();
337             phoneNumberList[j] = contactList.get(j).getPhoneNumber();
338             rawContactIdList[j] = contactList.get(j).getRawContactId();
339             dataIdList[j] = contactList.get(j).getDataId();
340             updateNameInEabDb(context, operation, phoneNameList[j],
341                     phoneNumberList[j], rawContactIdList[j], dataIdList[j]);
342             if (yieldPoint == j) {
343                 exceuteDB(context, operation);
344                 operation = null;
345                 operation = new ArrayList<ContentProviderOperation>();
346                 yieldPoint += 300;
347             }
348         }
349         exceuteDB(context, operation);
350     }
351 
updateNameInEabDb(Context context, ArrayList<ContentProviderOperation> ops, String phoneName, String phoneNumber, String rawContactId, String dataId)352     private static void updateNameInEabDb(Context context,
353             ArrayList<ContentProviderOperation> ops, String phoneName,
354             String phoneNumber, String rawContactId, String dataId) {
355         ContentValues values = new ContentValues();
356         values.put(EABContract.EABColumns.CONTACT_NAME, phoneName);
357 
358         String where = EABContract.EABColumns.CONTACT_NUMBER + " = ? AND "
359                 + EABContract.EABColumns.RAW_CONTACT_ID + " = ? AND "
360                 + EABContract.EABColumns.DATA_ID + " = ?";
361         ops.add(ContentProviderOperation
362                 .newUpdate(EABContract.EABColumns.CONTENT_URI)
363                 .withValues(values)
364                 .withSelection(where, new String[] { phoneNumber, rawContactId, dataId }).build());
365     }
366 
exceuteDB(Context context, ArrayList<ContentProviderOperation> ops)367     private static void exceuteDB(Context context, ArrayList<ContentProviderOperation> ops) {
368         if (ops.size() == 0) {
369             logger.debug("exceuteDB return as operation size is 0.");
370             return;
371         }
372         try {
373             context.getContentResolver().applyBatch(EABContract.AUTHORITY, ops);
374         } catch (RemoteException e) {
375             e.printStackTrace();
376         } catch (OperationApplicationException e) {
377             e.printStackTrace();
378         }
379         ops.clear();
380         logger.debug("exceuteDB return with successful operation.");
381         return;
382     }
383 
validateEligibleContact(Context context, String mdn)384     public static boolean validateEligibleContact(Context context, String mdn) {
385         boolean number = false;
386         if (null == mdn) {
387             logger.debug("validateEligibleContact - mdn is null.");
388             return number;
389         }
390         List<String> mdbList = new ArrayList<String>();
391         mdbList.add(mdn);
392         ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault();
393         mNumberUtils.setContext(context);
394         int numberType = mNumberUtils.validate(mdbList);
395         logger.debug("ContactNumberUtils.validate response : " + numberType);
396         if ( ContactNumberUtils.NUMBER_VALID == numberType) {
397             number = true;
398         }
399         logger.debug("Exiting validateEligibleContact with value : " + number);
400         return number;
401     }
402 
formatNumber(String mdn)403     public static String formatNumber(String mdn) {
404         logger.debug("Enter FormatNumber - mdn : " + mdn);
405         ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault();
406         return mNumberUtils.format(mdn);
407     }
408 
isSpecialNumber(String number)409     public static boolean isSpecialNumber(String number) {
410         logger.debug("Enter isSpecialNumber - number : " + number);
411         boolean result = false;
412         if (null != number) {
413             if (number.startsWith("*67") || number.startsWith("*82")) {
414                 result = true;
415             }
416         }
417         logger.debug("Exit isSpecialNumber - result : " + result);
418         return result;
419     }
420 }
421