1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.providers.contacts.database; 18 19import static android.provider.ContactsContract.Contacts; 20import static com.android.providers.contacts.ContactsDatabaseHelper.Tables; 21 22import android.content.ContentValues; 23import android.database.Cursor; 24import android.database.sqlite.SQLiteDatabase; 25import android.provider.ContactsContract; 26import android.text.TextUtils; 27 28import com.android.common.io.MoreCloseables; 29import com.android.providers.contacts.util.Clock; 30 31import java.util.Set; 32 33/** 34 * Methods for operating on the contacts table. 35 */ 36public class ContactsTableUtil { 37 38 /** 39 * Drop indexes if present. Create indexes. 40 * 41 * @param db The sqlite database instance. 42 */ 43 public static void createIndexes(SQLiteDatabase db) { 44 final String table = Tables.CONTACTS; 45 46 db.execSQL("CREATE INDEX contacts_has_phone_index ON " + table + " (" + 47 Contacts.HAS_PHONE_NUMBER + 48 ");"); 49 50 db.execSQL("CREATE INDEX contacts_name_raw_contact_id_index ON " + table + " (" + 51 Contacts.NAME_RAW_CONTACT_ID + 52 ");"); 53 54 db.execSQL(MoreDatabaseUtils.buildCreateIndexSql(table, 55 Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)); 56 } 57 58 public static void updateContactLastUpdateByContactId(SQLiteDatabase db, long contactId) { 59 final ContentValues values = new ContentValues(); 60 values.put(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP, 61 Clock.getInstance().currentTimeMillis()); 62 db.update(Tables.CONTACTS, values, Contacts._ID + " = ?", 63 new String[] {String.valueOf(contactId)}); 64 } 65 66 /** 67 * Refreshes the last updated timestamp of the contact with the current time. 68 * 69 * @param db The sqlite database instance. 70 * @param rawContactIds A set of raw contacts ids to refresh the contact for. 71 */ 72 public static void updateContactLastUpdateByRawContactId(SQLiteDatabase db, 73 Set<Long> rawContactIds) { 74 if (rawContactIds.isEmpty()) { 75 return; 76 } 77 78 db.execSQL(buildUpdateLastUpdateSql(rawContactIds)); 79 } 80 81 /** 82 * Build a sql to update the last updated timestamp for contacts. 83 * 84 * @param rawContactIds The raw contact ids that contacts should be updated for. 85 * @return The update sql statement. 86 */ 87 private static String buildUpdateLastUpdateSql(Set<Long> rawContactIds) { 88 // Not using bind args here due to sqlite bind arg size limit. Large number of bind args 89 // will cause a sqlite error: 90 // android.database.sqlite.SQLiteException: too many SQL variables (code 1) 91 // Sql injection is not possible because input is a set of Long. If any part of the sql 92 // is built with user input strings, then this must be converted to using bind args. 93 final String sql = "UPDATE " + Tables.CONTACTS 94 + " SET " + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " = " 95 + Clock.getInstance().currentTimeMillis() 96 + " WHERE " + Contacts._ID + " IN ( " 97 + " SELECT " + ContactsContract.RawContacts.CONTACT_ID 98 + " FROM " + Tables.RAW_CONTACTS 99 + " WHERE " + ContactsContract.RawContacts._ID 100 + " IN (" + TextUtils.join(",", rawContactIds) + ") " 101 + ")"; 102 return sql; 103 } 104 105 /** 106 * Delete a contact identified by the contact id. 107 * 108 * @param db The sqlite database instance. 109 * @param contactId The contact id to delete. 110 * @return The number of records deleted. 111 */ 112 public static int deleteContact(SQLiteDatabase db, long contactId) { 113 DeletedContactsTableUtil.insertDeletedContact(db, contactId); 114 return db.delete(Tables.CONTACTS, Contacts._ID + " = ?", new String[]{contactId + ""}); 115 } 116 117 /** 118 * Delete the aggregate contact if it has no constituent raw contacts other than the supplied 119 * one. 120 */ 121 public static void deleteContactIfSingleton(SQLiteDatabase db, long rawContactId) { 122 // This query will find a contact id if the contact has a raw contacts other than the one 123 // passed in. 124 final String sql = "select " + ContactsContract.RawContacts.CONTACT_ID + ", count(1)" 125 + " from " + Tables.RAW_CONTACTS 126 + " where " + ContactsContract.RawContacts.CONTACT_ID + " =" 127 + " (select " + ContactsContract.RawContacts.CONTACT_ID 128 + " from " + Tables.RAW_CONTACTS 129 + " where " + ContactsContract.RawContacts._ID + " = ?)" 130 + " group by " + ContactsContract.RawContacts.CONTACT_ID; 131 final Cursor cursor = db.rawQuery(sql, new String[]{rawContactId + ""}); 132 try { 133 if (cursor.moveToNext()) { 134 long contactId = cursor.getLong(0); 135 long numRawContacts = cursor.getLong(1); 136 137 if (numRawContacts == 1) { 138 // Only one raw contact, we can delete the parent. 139 deleteContact(db, contactId); 140 } 141 } 142 } finally { 143 MoreCloseables.closeQuietly(cursor); 144 } 145 } 146} 147