blob: 03665322c95b47957f65799e5ee3b80647e79c55 [file] [log] [blame]
/*
* 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 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;
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 com.android.providers.contacts.aggregation.ContactAggregator;
/**
* 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;
}
}