/* * Copyright (c) 2015, Motorola Mobility LLC * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - Neither the name of Motorola Mobility nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY LLC BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package com.android.service.ims.presence; import static com.android.service.ims.presence.AccountUtil.ACCOUNT_TYPE; import java.util.ArrayList; import java.util.List; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.CommonDataKinds.Phone; import com.android.ims.internal.ContactNumberUtils; import com.android.ims.internal.EABContract; import com.android.ims.internal.Logger; public class EABDbUtil { static private Logger logger = Logger.getLogger("EABDbUtil"); public static boolean validateAndSyncFromContactsDb(Context context) { logger.debug("Enter validateAndSyncFromContactsDb"); boolean response = true; // Get the last stored contact changed timestamp and sync only delta contacts. long contactLastChange = SharedPrefUtil.getLastContactChangedTimestamp(context, 0); logger.debug("contact last updated time before init :" + contactLastChange); ContentResolver contentResolver = context.getContentResolver(); String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.HAS_PHONE_NUMBER, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP }; String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + "> '0' AND " + ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " >'" + contactLastChange + "'"; String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " asc"; Cursor cursor = contentResolver.query(Contacts.CONTENT_URI, projection, selection, null, sortOrder); ArrayList allEligibleContacts = new ArrayList(); if (cursor != null) { logger.debug("cursor count : " + cursor.getCount()); } else { logger.debug("cursor = null"); } if (cursor != null && cursor.moveToFirst()) { do { String id = cursor.getString(cursor.getColumnIndex(Contacts._ID)); Long time = cursor.getLong(cursor.getColumnIndex( Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)); // Update the latest contact last modified timestamp. if (contactLastChange < time) { contactLastChange = time; } String[] commonDataKindsProjection = new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID, ContactsContract.CommonDataKinds.Phone.CONTACT_ID }; Cursor pCur = contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, commonDataKindsProjection, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[] { id }, null); ArrayList phoneNumList = new ArrayList(); if (pCur != null && pCur.moveToFirst()) { do { String contactNumber = pCur.getString(pCur.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER)); //contactNumber = filterEligibleContact(context, pContactNumber); if (validateEligibleContact(context, contactNumber)) { String contactName = pCur.getString(pCur.getColumnIndex( ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String rawContactId = pCur.getString(pCur.getColumnIndex( ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID)); String contactId = pCur.getString(pCur.getColumnIndex( ContactsContract.CommonDataKinds.Phone.CONTACT_ID)); // TODO: HACK - To be resolved as part of EAB Provider rework. if (phoneNumList.contains(contactNumber)) continue; phoneNumList.add(contactNumber); String dataId = getDataId(contentResolver,rawContactId, contactNumber); if (null != dataId) { allEligibleContacts.add(new PresenceContact(contactName, contactNumber, rawContactId, contactId, dataId)); } else { logger.debug("dataId is null. Don't add contact to " + "allEligibleContacts."); } } } while (pCur.moveToNext()); } if (pCur != null) { pCur.close(); } } while (cursor.moveToNext()); } if (null != cursor) { cursor.close(); } if (allEligibleContacts.size() > 0) { logger.debug("Adding : " + allEligibleContacts.size() + " new contact numbers to EAB db."); addContactsToEabDb(context, allEligibleContacts); logger.debug("contact last updated time after init :" + contactLastChange); SharedPrefUtil.saveLastContactChangedTimestamp(context, contactLastChange); SharedPrefUtil.saveLastContactDeletedTimestamp(context, contactLastChange); } logger.debug("Exit validateAndSyncFromContactsDb contact numbers synced : " + allEligibleContacts.size()); return response; } private static String getDataId(ContentResolver contentResolver, String rawContactId, String pContactNumber) { String dataId = null; String where = Data.RAW_CONTACT_ID + " = '" + rawContactId + "' AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + " AND " + Data.DATA1 + "='" + pContactNumber + "'"; Cursor cur = null; try { cur = contentResolver.query(Data.CONTENT_URI, new String[] { Data._ID }, where, null, null); if (cur.moveToFirst()) { dataId = cur.getString(cur.getColumnIndex(Data._ID)); } } catch (SQLiteException e) { logger.debug("SQLiteException while querying for dataId : " + e.toString()); } catch (Exception e) { logger.debug("Exception while querying for dataId : " + e); } finally { if (null != cur) { cur.close(); } } return dataId; } public static void addContactsToEabDb(Context context, ArrayList contactList) { ArrayList operation = new ArrayList(); logger.debug("Adding Contacts to EAB DB"); // To avoid the following exception - Too many content provider operations // between yield points. The maximum number of operations per yield point is // 500 for exceuteDB() int yieldPoint = 300; for (int j = 0; j < contactList.size(); j++) { addContactToEabDb(context, operation, contactList.get(j).getDisplayName(), contactList.get(j).getPhoneNumber(), contactList.get(j).getRawContactId(), contactList.get(j).getContactId(), contactList.get(j).getDataId()); if (yieldPoint == j) { exceuteDB(context, operation); operation = null; operation = new ArrayList(); yieldPoint += 300; } } exceuteDB(context, operation); } private static void addContactToEabDb( Context context, ArrayList ops, String displayName, String phoneNumber, String rawContactId, String contactId, String dataId) { ops.add(ContentProviderOperation .newInsert(EABContract.EABColumns.CONTENT_URI) .withValue(EABContract.EABColumns.CONTACT_NAME, displayName) .withValue(EABContract.EABColumns.CONTACT_NUMBER, phoneNumber) .withValue(EABContract.EABColumns.ACCOUNT_TYPE, ACCOUNT_TYPE) .withValue(EABContract.EABColumns.RAW_CONTACT_ID, rawContactId) .withValue(EABContract.EABColumns.CONTACT_ID, contactId) .withValue(EABContract.EABColumns.DATA_ID, dataId).build()); } public static void deleteContactsFromEabDb(Context context, ArrayList contactList) { ArrayList operation = new ArrayList(); logger.debug("Deleting Contacts from EAB DB"); String[] contactIdList = new String[contactList.size()]; // To avoid the following exception - Too many content provider operations // between yield points. The maximum number of operations per yield point is // 500 for exceuteDB() int yieldPoint = 300; for (int j = 0; j < contactList.size(); j++) { contactIdList[j] = contactList.get(j).getContactId().toString(); deleteContactFromEabDb(context, operation, contactIdList[j]); if (yieldPoint == j) { exceuteDB(context, operation); operation = null; operation = new ArrayList(); yieldPoint += 300; } } exceuteDB(context, operation); } private static void deleteContactFromEabDb(Context context, ArrayList ops, String contactId) { // Add operation only if there is an entry in EABProvider table. String[] eabProjection = new String[] { EABContract.EABColumns.CONTACT_NUMBER, EABContract.EABColumns.CONTACT_ID }; String eabWhereClause = null; if (ContactsContract.Profile.MIN_ID == Long.valueOf(contactId)) { eabWhereClause = EABContract.EABColumns.CONTACT_ID + " >='" + contactId + "'"; } else { eabWhereClause = EABContract.EABColumns.CONTACT_ID + " ='" + contactId + "'"; } Cursor eabDeleteCursor = context.getContentResolver().query( EABContract.EABColumns.CONTENT_URI, eabProjection, eabWhereClause, null, null); if (null != eabDeleteCursor) { int count = eabDeleteCursor.getCount(); logger.debug("cursor count : " + count); if (count > 0) { eabDeleteCursor.moveToNext(); long eabDeleteContactId = eabDeleteCursor. getLong(eabDeleteCursor.getColumnIndex(EABContract.EABColumns.CONTACT_ID)); logger.debug("eabDeleteContactId : " + eabDeleteContactId); if (ContactsContract.Profile.MIN_ID == Long.valueOf(contactId)) { if (ContactsContract.isProfileId(eabDeleteContactId)) { logger.debug("Deleting Profile contact."); ops.add(ContentProviderOperation .newDelete(EABContract.EABColumns.CONTENT_URI) .withSelection(EABContract.EABColumns.CONTACT_ID + " >= ?", new String[] { contactId }).build()); } else { logger.debug("Not a Profile contact. Do nothing."); } } else { ops.add(ContentProviderOperation .newDelete(EABContract.EABColumns.CONTENT_URI) .withSelection(EABContract.EABColumns.CONTACT_ID + " = ?", new String[] { contactId }).build()); } } eabDeleteCursor.close(); } } public static void deleteNumbersFromEabDb(Context context, ArrayList contactList) { ArrayList operation = new ArrayList(); logger.debug("Deleting Number from EAB DB"); String[] rawContactIdList = new String [contactList.size()]; String[] DataIdList = new String [contactList.size()]; // To avoid the following exception - Too many content provider operations // between yield points. The maximum number of operations per yield point is // 500 for exceuteDB() int yieldPoint = 300; for (int j = 0; j < contactList.size(); j++) { rawContactIdList[j] = contactList.get(j).getRawContactId(); DataIdList[j] = contactList.get(j).getDataId(); deleteNumberFromEabDb(context, operation, rawContactIdList[j], DataIdList[j]); if (yieldPoint == j) { exceuteDB(context, operation); operation = null; operation = new ArrayList(); yieldPoint += 300; } } exceuteDB(context, operation); } private static void deleteNumberFromEabDb(Context context, ArrayList ops, String rawContactId, String dataId) { // Add operation only if there is an entry in EABProvider table. String[] eabProjection = new String[] { EABContract.EABColumns.CONTACT_NUMBER }; String eabWhereClause = EABContract.EABColumns.RAW_CONTACT_ID + " ='" + rawContactId + "' AND " + EABContract.EABColumns.DATA_ID + " ='" + dataId + "'"; Cursor eabDeleteCursor = context.getContentResolver().query( EABContract.EABColumns.CONTENT_URI, eabProjection, eabWhereClause, null, null); if (null != eabDeleteCursor) { int count = eabDeleteCursor.getCount(); logger.debug("Delete number cursor count : " + count); if (count > 0) { ops.add(ContentProviderOperation.newDelete(EABContract.EABColumns.CONTENT_URI) .withSelection(EABContract.EABColumns.RAW_CONTACT_ID + " = ? AND " + EABContract.EABColumns.DATA_ID + " = ?", new String[] { rawContactId, dataId }).build()); } eabDeleteCursor.close(); } } public static void updateNamesInEabDb(Context context, ArrayList contactList) { ArrayList operation = new ArrayList(); logger.debug("Update name in EAB DB"); String[] phoneNameList = new String[contactList.size()]; String[] phoneNumberList = new String[contactList.size()]; String[] rawContactIdList = new String [contactList.size()]; String[] dataIdList = new String [contactList.size()]; // To avoid the following exception - Too many content provider operations // between yield points. The maximum number of operations per yield point is // 500 for exceuteDB() int yieldPoint = 300; for (int j = 0; j < contactList.size(); j++) { phoneNameList[j] = contactList.get(j).getDisplayName(); phoneNumberList[j] = contactList.get(j).getPhoneNumber(); rawContactIdList[j] = contactList.get(j).getRawContactId(); dataIdList[j] = contactList.get(j).getDataId(); updateNameInEabDb(context, operation, phoneNameList[j], phoneNumberList[j], rawContactIdList[j], dataIdList[j]); if (yieldPoint == j) { exceuteDB(context, operation); operation = null; operation = new ArrayList(); yieldPoint += 300; } } exceuteDB(context, operation); } private static void updateNameInEabDb(Context context, ArrayList ops, String phoneName, String phoneNumber, String rawContactId, String dataId) { ContentValues values = new ContentValues(); values.put(EABContract.EABColumns.CONTACT_NAME, phoneName); String where = EABContract.EABColumns.CONTACT_NUMBER + " = ? AND " + EABContract.EABColumns.RAW_CONTACT_ID + " = ? AND " + EABContract.EABColumns.DATA_ID + " = ?"; ops.add(ContentProviderOperation .newUpdate(EABContract.EABColumns.CONTENT_URI) .withValues(values) .withSelection(where, new String[] { phoneNumber, rawContactId, dataId }).build()); } private static void exceuteDB(Context context, ArrayList ops) { if (ops.size() == 0) { logger.debug("exceuteDB return as operation size is 0."); return; } try { context.getContentResolver().applyBatch(EABContract.AUTHORITY, ops); } catch (RemoteException e) { e.printStackTrace(); } catch (OperationApplicationException e) { e.printStackTrace(); } ops.clear(); logger.debug("exceuteDB return with successful operation."); return; } public static boolean validateEligibleContact(Context context, String mdn) { boolean number = false; if (null == mdn) { logger.debug("validateEligibleContact - mdn is null."); return number; } List mdbList = new ArrayList(); mdbList.add(mdn); ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault(); mNumberUtils.setContext(context); int numberType = mNumberUtils.validate(mdbList); logger.debug("ContactNumberUtils.validate response : " + numberType); if ( ContactNumberUtils.NUMBER_VALID == numberType) { number = true; } return number; } public static String filterEligibleContact(Context context, String mdn) { String number = null; if (null == mdn) { logger.debug("filterEligibleContact - mdn is null."); return number; } logger.debug("Before filterEligibleContact validation : " + mdn); List mdbList = new ArrayList(); mdbList.add(mdn); ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault(); mNumberUtils.setContext(context); int numberType = mNumberUtils.validate(mdbList); logger.debug("ContactNumberUtils.validate response : " + numberType); if ( ContactNumberUtils.NUMBER_VALID == numberType) { String[] mdnFormatted = mNumberUtils.format(mdbList); if (mdnFormatted.length > 0 ){ number = mdnFormatted[0]; } } return number; } }