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 29package com.android.service.ims.presence; 30 31import static com.android.service.ims.presence.AccountUtil.ACCOUNT_TYPE; 32 33import java.util.ArrayList; 34import java.util.List; 35 36import android.content.ContentProviderOperation; 37import android.content.ContentResolver; 38import android.content.ContentValues; 39import android.content.Context; 40import android.content.OperationApplicationException; 41import android.database.Cursor; 42import android.database.sqlite.SQLiteException; 43import android.os.RemoteException; 44import android.provider.ContactsContract; 45import android.provider.ContactsContract.Contacts; 46import android.provider.ContactsContract.Data; 47import android.provider.ContactsContract.CommonDataKinds.Phone; 48 49import com.android.ims.internal.ContactNumberUtils; 50import com.android.ims.internal.EABContract; 51import com.android.ims.internal.Logger; 52 53public class EABDbUtil { 54 static private Logger logger = Logger.getLogger("EABDbUtil"); 55 56 public static boolean validateAndSyncFromContactsDb(Context context) { 57 logger.debug("Enter validateAndSyncFromContactsDb"); 58 boolean response = true; 59 // Get the last stored contact changed timestamp and sync only delta contacts. 60 long contactLastChange = SharedPrefUtil.getLastContactChangedTimestamp(context, 0); 61 logger.debug("contact last updated time before init :" + contactLastChange); 62 ContentResolver contentResolver = context.getContentResolver(); 63 String[] projection = new String[] { ContactsContract.Contacts._ID, 64 ContactsContract.Contacts.HAS_PHONE_NUMBER, 65 ContactsContract.Contacts.DISPLAY_NAME, 66 ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP }; 67 String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + "> '0' AND " 68 + ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP 69 + " >'" + contactLastChange + "'"; 70 String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " asc"; 71 Cursor cursor = contentResolver.query(Contacts.CONTENT_URI, projection, selection, 72 null, sortOrder); 73 ArrayList<PresenceContact> allEligibleContacts = new ArrayList<PresenceContact>(); 74 75 if (cursor != null) { 76 logger.debug("cursor count : " + cursor.getCount()); 77 } else { 78 logger.debug("cursor = null"); 79 } 80 81 if (cursor != null && cursor.moveToFirst()) { 82 do { 83 String id = cursor.getString(cursor.getColumnIndex(Contacts._ID)); 84 Long time = cursor.getLong(cursor.getColumnIndex( 85 Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)); 86 // Update the latest contact last modified timestamp. 87 if (contactLastChange < time) { 88 contactLastChange = time; 89 } 90 String[] commonDataKindsProjection = new String[] { 91 ContactsContract.CommonDataKinds.Phone.NUMBER, 92 ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, 93 ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID, 94 ContactsContract.CommonDataKinds.Phone.CONTACT_ID }; 95 Cursor pCur = contentResolver.query( 96 ContactsContract.CommonDataKinds.Phone.CONTENT_URI, 97 commonDataKindsProjection, 98 ContactsContract.CommonDataKinds.Phone.CONTACT_ID 99 + " = ?", new String[] { id }, null); 100 ArrayList<String> phoneNumList = new ArrayList<String>(); 101 102 if (pCur != null && pCur.moveToFirst()) { 103 do { 104 String contactNumber = pCur.getString(pCur.getColumnIndex( 105 ContactsContract.CommonDataKinds.Phone.NUMBER)); 106 //contactNumber = filterEligibleContact(context, pContactNumber); 107 if (validateEligibleContact(context, contactNumber)) { 108 String contactName = pCur.getString(pCur.getColumnIndex( 109 ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); 110 String rawContactId = pCur.getString(pCur.getColumnIndex( 111 ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID)); 112 String contactId = pCur.getString(pCur.getColumnIndex( 113 ContactsContract.CommonDataKinds.Phone.CONTACT_ID)); 114 // TODO: HACK - To be resolved as part of EAB Provider rework. 115 if (phoneNumList.contains(contactNumber)) continue; 116 phoneNumList.add(contactNumber); 117 118 String dataId = getDataId(contentResolver,rawContactId, contactNumber); 119 if (null != dataId) { 120 allEligibleContacts.add(new PresenceContact(contactName, 121 contactNumber, rawContactId, contactId, dataId)); 122 } else { 123 logger.debug("dataId is null. Don't add contact to " + 124 "allEligibleContacts."); 125 } 126 } 127 } while (pCur.moveToNext()); 128 } 129 if (pCur != null) { 130 pCur.close(); 131 } 132 } while (cursor.moveToNext()); 133 } 134 if (null != cursor) { 135 cursor.close(); 136 } 137 if (allEligibleContacts.size() > 0) { 138 logger.debug("Adding : " + allEligibleContacts.size() + 139 " new contact numbers to EAB db."); 140 addContactsToEabDb(context, allEligibleContacts); 141 logger.debug("contact last updated time after init :" + contactLastChange); 142 SharedPrefUtil.saveLastContactChangedTimestamp(context, contactLastChange); 143 SharedPrefUtil.saveLastContactDeletedTimestamp(context, contactLastChange); 144 } 145 logger.debug("Exit validateAndSyncFromContactsDb contact numbers synced : " + 146 allEligibleContacts.size()); 147 return response; 148 } 149 150 private static String getDataId(ContentResolver contentResolver, 151 String rawContactId, String pContactNumber) { 152 String dataId = null; 153 String where = Data.RAW_CONTACT_ID + " = '" + rawContactId + "' AND " 154 + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" 155 + " AND " + Data.DATA1 + "='" + pContactNumber + "'"; 156 Cursor cur = null; 157 try { 158 cur = contentResolver.query(Data.CONTENT_URI, 159 new String[] { Data._ID }, where, null, null); 160 if (cur.moveToFirst()) { 161 dataId = cur.getString(cur.getColumnIndex(Data._ID)); 162 } 163 } catch (SQLiteException e) { 164 logger.debug("SQLiteException while querying for dataId : " + e.toString()); 165 } catch (Exception e) { 166 logger.debug("Exception while querying for dataId : " + e); 167 } finally { 168 if (null != cur) { 169 cur.close(); 170 } 171 } 172 return dataId; 173 } 174 175 public static void addContactsToEabDb(Context context, 176 ArrayList<PresenceContact> contactList) { 177 ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>(); 178 179 logger.debug("Adding Contacts to EAB DB"); 180 // To avoid the following exception - Too many content provider operations 181 // between yield points. The maximum number of operations per yield point is 182 // 500 for exceuteDB() 183 int yieldPoint = 300; 184 for (int j = 0; j < contactList.size(); j++) { 185 addContactToEabDb(context, operation, contactList.get(j).getDisplayName(), 186 contactList.get(j).getPhoneNumber(), contactList.get(j).getRawContactId(), 187 contactList.get(j).getContactId(), contactList.get(j).getDataId()); 188 if (yieldPoint == j) { 189 exceuteDB(context, operation); 190 operation = null; 191 operation = new ArrayList<ContentProviderOperation>(); 192 yieldPoint += 300; 193 } 194 } 195 exceuteDB(context, operation); 196 } 197 198 private static void addContactToEabDb( 199 Context context, ArrayList<ContentProviderOperation> ops, String displayName, 200 String phoneNumber, String rawContactId, String contactId, 201 String dataId) { 202 ops.add(ContentProviderOperation 203 .newInsert(EABContract.EABColumns.CONTENT_URI) 204 .withValue(EABContract.EABColumns.CONTACT_NAME, displayName) 205 .withValue(EABContract.EABColumns.CONTACT_NUMBER, phoneNumber) 206 .withValue(EABContract.EABColumns.ACCOUNT_TYPE, ACCOUNT_TYPE) 207 .withValue(EABContract.EABColumns.RAW_CONTACT_ID, rawContactId) 208 .withValue(EABContract.EABColumns.CONTACT_ID, contactId) 209 .withValue(EABContract.EABColumns.DATA_ID, dataId).build()); 210 } 211 212 public static void deleteContactsFromEabDb(Context context, 213 ArrayList<PresenceContact> contactList) { 214 ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>(); 215 216 logger.debug("Deleting Contacts from EAB DB"); 217 String[] contactIdList = new String[contactList.size()]; 218 // To avoid the following exception - Too many content provider operations 219 // between yield points. The maximum number of operations per yield point is 220 // 500 for exceuteDB() 221 int yieldPoint = 300; 222 for (int j = 0; j < contactList.size(); j++) { 223 contactIdList[j] = contactList.get(j).getContactId().toString(); 224 deleteContactFromEabDb(context, operation, contactIdList[j]); 225 if (yieldPoint == j) { 226 exceuteDB(context, operation); 227 operation = null; 228 operation = new ArrayList<ContentProviderOperation>(); 229 yieldPoint += 300; 230 } 231 } 232 exceuteDB(context, operation); 233 } 234 235 private static void deleteContactFromEabDb(Context context, 236 ArrayList<ContentProviderOperation> ops, String contactId) { 237 // Add operation only if there is an entry in EABProvider table. 238 String[] eabProjection = new String[] { 239 EABContract.EABColumns.CONTACT_NUMBER, 240 EABContract.EABColumns.CONTACT_ID }; 241 String eabWhereClause = null; 242 if (ContactsContract.Profile.MIN_ID == Long.valueOf(contactId)) { 243 eabWhereClause = EABContract.EABColumns.CONTACT_ID + " >='" + contactId + "'"; 244 } else { 245 eabWhereClause = EABContract.EABColumns.CONTACT_ID + " ='" + contactId + "'"; 246 } 247 Cursor eabDeleteCursor = context.getContentResolver().query( 248 EABContract.EABColumns.CONTENT_URI, eabProjection, 249 eabWhereClause, null, null); 250 if (null != eabDeleteCursor) { 251 int count = eabDeleteCursor.getCount(); 252 logger.debug("cursor count : " + count); 253 if (count > 0) { 254 eabDeleteCursor.moveToNext(); 255 long eabDeleteContactId = eabDeleteCursor. 256 getLong(eabDeleteCursor.getColumnIndex(EABContract.EABColumns.CONTACT_ID)); 257 logger.debug("eabDeleteContactId : " + eabDeleteContactId); 258 if (ContactsContract.Profile.MIN_ID == Long.valueOf(contactId)) { 259 if (ContactsContract.isProfileId(eabDeleteContactId)) { 260 logger.debug("Deleting Profile contact."); 261 ops.add(ContentProviderOperation 262 .newDelete(EABContract.EABColumns.CONTENT_URI) 263 .withSelection(EABContract.EABColumns.CONTACT_ID + " >= ?", 264 new String[] { contactId }).build()); 265 } else { 266 logger.debug("Not a Profile contact. Do nothing."); 267 } 268 } else { 269 ops.add(ContentProviderOperation 270 .newDelete(EABContract.EABColumns.CONTENT_URI) 271 .withSelection(EABContract.EABColumns.CONTACT_ID + " = ?", 272 new String[] { contactId }).build()); 273 } 274 } 275 eabDeleteCursor.close(); 276 } 277 278 } 279 280 public static void deleteNumbersFromEabDb(Context context, 281 ArrayList<PresenceContact> contactList) { 282 ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>(); 283 284 logger.debug("Deleting Number from EAB DB"); 285 String[] rawContactIdList = new String [contactList.size()]; 286 String[] DataIdList = new String [contactList.size()]; 287 // To avoid the following exception - Too many content provider operations 288 // between yield points. The maximum number of operations per yield point is 289 // 500 for exceuteDB() 290 int yieldPoint = 300; 291 for (int j = 0; j < contactList.size(); j++) { 292 rawContactIdList[j] = contactList.get(j).getRawContactId(); 293 DataIdList[j] = contactList.get(j).getDataId(); 294 deleteNumberFromEabDb(context, operation, rawContactIdList[j], DataIdList[j]); 295 if (yieldPoint == j) { 296 exceuteDB(context, operation); 297 operation = null; 298 operation = new ArrayList<ContentProviderOperation>(); 299 yieldPoint += 300; 300 } 301 } 302 exceuteDB(context, operation); 303 } 304 305 private static void deleteNumberFromEabDb(Context context, 306 ArrayList<ContentProviderOperation> ops, String rawContactId, String dataId) { 307 // Add operation only if there is an entry in EABProvider table. 308 String[] eabProjection = new String[] { 309 EABContract.EABColumns.CONTACT_NUMBER }; 310 String eabWhereClause = EABContract.EABColumns.RAW_CONTACT_ID + " ='" + rawContactId 311 + "' AND " + EABContract.EABColumns.DATA_ID + " ='" + dataId + "'"; 312 Cursor eabDeleteCursor = context.getContentResolver().query( 313 EABContract.EABColumns.CONTENT_URI, eabProjection, 314 eabWhereClause, null, null); 315 if (null != eabDeleteCursor) { 316 int count = eabDeleteCursor.getCount(); 317 logger.debug("Delete number cursor count : " + count); 318 if (count > 0) { 319 ops.add(ContentProviderOperation.newDelete(EABContract.EABColumns.CONTENT_URI) 320 .withSelection(EABContract.EABColumns.RAW_CONTACT_ID + " = ? AND " 321 + EABContract.EABColumns.DATA_ID + " = ?", 322 new String[] { rawContactId, dataId }).build()); 323 } 324 eabDeleteCursor.close(); 325 } 326 } 327 328 public static void updateNamesInEabDb(Context context, 329 ArrayList<PresenceContact> contactList) { 330 ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>(); 331 332 logger.debug("Update name in EAB DB"); 333 String[] phoneNameList = new String[contactList.size()]; 334 String[] phoneNumberList = new String[contactList.size()]; 335 String[] rawContactIdList = new String [contactList.size()]; 336 String[] dataIdList = new String [contactList.size()]; 337 // To avoid the following exception - Too many content provider operations 338 // between yield points. The maximum number of operations per yield point is 339 // 500 for exceuteDB() 340 int yieldPoint = 300; 341 for (int j = 0; j < contactList.size(); j++) { 342 phoneNameList[j] = contactList.get(j).getDisplayName(); 343 phoneNumberList[j] = contactList.get(j).getPhoneNumber(); 344 rawContactIdList[j] = contactList.get(j).getRawContactId(); 345 dataIdList[j] = contactList.get(j).getDataId(); 346 updateNameInEabDb(context, operation, phoneNameList[j], 347 phoneNumberList[j], rawContactIdList[j], dataIdList[j]); 348 if (yieldPoint == j) { 349 exceuteDB(context, operation); 350 operation = null; 351 operation = new ArrayList<ContentProviderOperation>(); 352 yieldPoint += 300; 353 } 354 } 355 exceuteDB(context, operation); 356 } 357 358 private static void updateNameInEabDb(Context context, 359 ArrayList<ContentProviderOperation> ops, String phoneName, 360 String phoneNumber, String rawContactId, String dataId) { 361 ContentValues values = new ContentValues(); 362 values.put(EABContract.EABColumns.CONTACT_NAME, phoneName); 363 364 String where = EABContract.EABColumns.CONTACT_NUMBER + " = ? AND " 365 + EABContract.EABColumns.RAW_CONTACT_ID + " = ? AND " 366 + EABContract.EABColumns.DATA_ID + " = ?"; 367 ops.add(ContentProviderOperation 368 .newUpdate(EABContract.EABColumns.CONTENT_URI) 369 .withValues(values) 370 .withSelection(where, new String[] { phoneNumber, rawContactId, dataId }).build()); 371 } 372 373 private static void exceuteDB(Context context, ArrayList<ContentProviderOperation> ops) { 374 if (ops.size() == 0) { 375 logger.debug("exceuteDB return as operation size is 0."); 376 return; 377 } 378 try { 379 context.getContentResolver().applyBatch(EABContract.AUTHORITY, ops); 380 } catch (RemoteException e) { 381 e.printStackTrace(); 382 } catch (OperationApplicationException e) { 383 e.printStackTrace(); 384 } 385 ops.clear(); 386 logger.debug("exceuteDB return with successful operation."); 387 return; 388 } 389 390 public static boolean validateEligibleContact(Context context, String mdn) { 391 boolean number = false; 392 if (null == mdn) { 393 logger.debug("validateEligibleContact - mdn is null."); 394 return number; 395 } 396 List<String> mdbList = new ArrayList<String>(); 397 mdbList.add(mdn); 398 ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault(); 399 mNumberUtils.setContext(context); 400 int numberType = mNumberUtils.validate(mdbList); 401 logger.debug("ContactNumberUtils.validate response : " + numberType); 402 if ( ContactNumberUtils.NUMBER_VALID == numberType) { 403 number = true; 404 } 405 return number; 406 } 407 408 public static String filterEligibleContact(Context context, String mdn) { 409 String number = null; 410 if (null == mdn) { 411 logger.debug("filterEligibleContact - mdn is null."); 412 return number; 413 } 414 logger.debug("Before filterEligibleContact validation : " + mdn); 415 List<String> mdbList = new ArrayList<String>(); 416 mdbList.add(mdn); 417 ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault(); 418 mNumberUtils.setContext(context); 419 int numberType = mNumberUtils.validate(mdbList); 420 logger.debug("ContactNumberUtils.validate response : " + numberType); 421 if ( ContactNumberUtils.NUMBER_VALID == numberType) { 422 String[] mdnFormatted = mNumberUtils.format(mdbList); 423 if (mdnFormatted.length > 0 ){ 424 number = mdnFormatted[0]; 425 } 426 } 427 return number; 428 } 429} 430