/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License */ package com.android.providers.contacts; import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns; import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns; import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns; import com.android.providers.contacts.ContactsDatabaseHelper.Tables; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Nickname; import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.Data; import android.text.TextUtils; /** * Handles inserts and update for a specific Data type. */ public abstract class DataRowHandler { public interface DataDeleteQuery { public static final String TABLE = Tables.DATA_JOIN_MIMETYPES; public static final String[] CONCRETE_COLUMNS = new String[] { DataColumns.CONCRETE_ID, MimetypesColumns.MIMETYPE, Data.RAW_CONTACT_ID, Data.IS_PRIMARY, Data.DATA1, }; public static final String[] COLUMNS = new String[] { Data._ID, MimetypesColumns.MIMETYPE, Data.RAW_CONTACT_ID, Data.IS_PRIMARY, Data.DATA1, }; public static final int _ID = 0; public static final int MIMETYPE = 1; public static final int RAW_CONTACT_ID = 2; public static final int IS_PRIMARY = 3; public static final int DATA1 = 4; } public interface DataUpdateQuery { String[] COLUMNS = { Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE }; int _ID = 0; int RAW_CONTACT_ID = 1; int MIMETYPE = 2; } protected final Context mContext; protected final ContactsDatabaseHelper mDbHelper; protected final ContactAggregator mContactAggregator; protected String[] mSelectionArgs1 = new String[1]; protected final String mMimetype; protected long mMimetypeId; @SuppressWarnings("all") public DataRowHandler(Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, String mimetype) { mContext = context; mDbHelper = dbHelper; mContactAggregator = aggregator; mMimetype = mimetype; // To ensure the data column position. This is dead code if properly configured. if (StructuredName.DISPLAY_NAME != Data.DATA1 || Nickname.NAME != Data.DATA1 || Organization.COMPANY != Data.DATA1 || Phone.NUMBER != Data.DATA1 || Email.DATA != Data.DATA1) { throw new AssertionError("Some of ContactsContract.CommonDataKinds class primary" + " data is not in DATA1 column"); } } protected long getMimeTypeId() { if (mMimetypeId == 0) { mMimetypeId = mDbHelper.getMimeTypeId(mMimetype); } return mMimetypeId; } /** * Inserts a row into the {@link Data} table. */ public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, ContentValues values) { final long dataId = db.insert(Tables.DATA, null, values); final Integer primary = values.getAsInteger(Data.IS_PRIMARY); final Integer superPrimary = values.getAsInteger(Data.IS_SUPER_PRIMARY); if ((primary != null && primary != 0) || (superPrimary != null && superPrimary != 0)) { final long mimeTypeId = getMimeTypeId(); mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); // We also have to make sure that no other data item on this raw_contact is // configured super primary if (superPrimary != null) { if (superPrimary != 0) { mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); } else { mDbHelper.clearSuperPrimary(rawContactId, mimeTypeId); } } else { // if there is already another data item configured as super-primary, // take over the flag (which will automatically remove it from the other item) if (mDbHelper.rawContactHasSuperPrimary(rawContactId, mimeTypeId)) { mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); } } } if (containsSearchableColumns(values)) { txContext.invalidateSearchIndexForRawContact(rawContactId); } return dataId; } /** * Validates data and updates a {@link Data} row using the cursor, which contains * the current data. * * @return true if update changed something */ public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, Cursor c, boolean callerIsSyncAdapter) { long dataId = c.getLong(DataUpdateQuery._ID); long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); handlePrimaryAndSuperPrimary(values, dataId, rawContactId); if (values.size() > 0) { mSelectionArgs1[0] = String.valueOf(dataId); db.update(Tables.DATA, values, Data._ID + " =?", mSelectionArgs1); } if (containsSearchableColumns(values)) { txContext.invalidateSearchIndexForRawContact(rawContactId); } if (!callerIsSyncAdapter) { txContext.markRawContactDirty(rawContactId); } return true; } public boolean hasSearchableData() { return false; } public boolean containsSearchableColumns(ContentValues values) { return false; } public void appendSearchableData(SearchIndexManager.IndexBuilder builder) { } /** * Ensures that all super-primary and primary flags of this raw_contact are * configured correctly */ private void handlePrimaryAndSuperPrimary(ContentValues values, long dataId, long rawContactId) { final boolean hasPrimary = values.containsKey(Data.IS_PRIMARY); final boolean hasSuperPrimary = values.containsKey(Data.IS_SUPER_PRIMARY); // Nothing to do? Bail out early if (!hasPrimary && !hasSuperPrimary) return; final long mimeTypeId = getMimeTypeId(); // Check if we want to clear values final boolean clearPrimary = hasPrimary && values.getAsInteger(Data.IS_PRIMARY) == 0; final boolean clearSuperPrimary = hasSuperPrimary && values.getAsInteger(Data.IS_SUPER_PRIMARY) == 0; if (clearPrimary || clearSuperPrimary) { // Test whether these values are currently set mSelectionArgs1[0] = String.valueOf(dataId); final String[] cols = new String[] { Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY }; final Cursor c = mDbHelper.getReadableDatabase().query(Tables.DATA, cols, Data._ID + "=?", mSelectionArgs1, null, null, null); try { if (c.moveToFirst()) { final boolean isPrimary = c.getInt(0) != 0; final boolean isSuperPrimary = c.getInt(1) != 0; // Clear values if they are currently set if (isSuperPrimary) { mDbHelper.clearSuperPrimary(rawContactId, mimeTypeId); } if (clearPrimary && isPrimary) { mDbHelper.setIsPrimary(rawContactId, -1, mimeTypeId); } } } finally { c.close(); } } else { // Check if we want to set values final boolean setPrimary = hasPrimary && values.getAsInteger(Data.IS_PRIMARY) != 0; final boolean setSuperPrimary = hasSuperPrimary && values.getAsInteger(Data.IS_SUPER_PRIMARY) != 0; if (setSuperPrimary) { // Set both super primary and primary mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); } else if (setPrimary) { // Primary was explicitly set, but super-primary was not. // In this case we set super-primary on this data item, if // any data item of the same raw-contact already is super-primary if (mDbHelper.rawContactHasSuperPrimary(rawContactId, mimeTypeId)) { mDbHelper.setIsSuperPrimary(rawContactId, dataId, mimeTypeId); } mDbHelper.setIsPrimary(rawContactId, dataId, mimeTypeId); } } // Now that we've taken care of clearing this, remove it from "values". values.remove(Data.IS_SUPER_PRIMARY); values.remove(Data.IS_PRIMARY); } public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { long dataId = c.getLong(DataDeleteQuery._ID); long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); boolean primary = c.getInt(DataDeleteQuery.IS_PRIMARY) != 0; mSelectionArgs1[0] = String.valueOf(dataId); int count = db.delete(Tables.DATA, Data._ID + "=?", mSelectionArgs1); mSelectionArgs1[0] = String.valueOf(rawContactId); db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=?", mSelectionArgs1); if (count != 0 && primary) { fixPrimary(db, rawContactId); } if (hasSearchableData()) { txContext.invalidateSearchIndexForRawContact(rawContactId); } return count; } private void fixPrimary(SQLiteDatabase db, long rawContactId) { long mimeTypeId = getMimeTypeId(); long primaryId = -1; int primaryType = -1; mSelectionArgs1[0] = String.valueOf(rawContactId); Cursor c = db.query(DataDeleteQuery.TABLE, DataDeleteQuery.CONCRETE_COLUMNS, Data.RAW_CONTACT_ID + "=?" + " AND " + DataColumns.MIMETYPE_ID + "=" + mimeTypeId, mSelectionArgs1, null, null, null); try { while (c.moveToNext()) { long dataId = c.getLong(DataDeleteQuery._ID); int type = c.getInt(DataDeleteQuery.DATA1); if (primaryType == -1 || getTypeRank(type) < getTypeRank(primaryType)) { primaryId = dataId; primaryType = type; } } } finally { c.close(); } if (primaryId != -1) { mDbHelper.setIsPrimary(rawContactId, primaryId, mimeTypeId); } } /** * Returns the rank of a specific record type to be used in determining the primary * row. Lower number represents higher priority. */ protected int getTypeRank(int type) { return 0; } protected void fixRawContactDisplayName(SQLiteDatabase db, TransactionContext txContext, long rawContactId) { if (!isNewRawContact(txContext, rawContactId)) { mDbHelper.updateRawContactDisplayName(db, rawContactId); mContactAggregator.updateDisplayNameForRawContact(db, rawContactId); } } private boolean isNewRawContact(TransactionContext txContext, long rawContactId) { return txContext.isNewRawContact(rawContactId); } /** * Return set of values, using current values at given {@link Data#_ID} * as baseline, but augmented with any updates. Returns null if there is * no change. */ public ContentValues getAugmentedValues(SQLiteDatabase db, long dataId, ContentValues update) { boolean changing = false; final ContentValues values = new ContentValues(); mSelectionArgs1[0] = String.valueOf(dataId); final Cursor cursor = db.query(Tables.DATA, null, Data._ID + "=?", mSelectionArgs1, null, null, null); try { if (cursor.moveToFirst()) { for (int i = 0; i < cursor.getColumnCount(); i++) { final String key = cursor.getColumnName(i); final String value = cursor.getString(i); if (!changing && update.containsKey(key)) { Object newValue = update.get(key); String newString = newValue == null ? null : newValue.toString(); changing |= !TextUtils.equals(newString, value); } values.put(key, value); } } } finally { cursor.close(); } if (!changing) { return null; } values.putAll(update); return values; } public void triggerAggregation(TransactionContext txContext, long rawContactId) { mContactAggregator.triggerAggregation(txContext, rawContactId); } /** * Test all against {@link TextUtils#isEmpty(CharSequence)}. */ public boolean areAllEmpty(ContentValues values, String[] keys) { for (String key : keys) { if (!TextUtils.isEmpty(values.getAsString(key))) { return false; } } return true; } /** * Returns true if a value (possibly null) is specified for at least one of the supplied keys. */ public boolean areAnySpecified(ContentValues values, String[] keys) { for (String key : keys) { if (values.containsKey(key)) { return true; } } return false; } }