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