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