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