blob: 3d28b05c96ebf49bce867ce5640fbfbe85bb02c5 [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.Photo;
import android.util.Log;
import com.android.providers.contacts.aggregation.AbstractContactAggregator;
import java.io.IOException;
/**
* Handler for photo data rows.
*/
public class DataRowHandlerForPhoto extends DataRowHandler {
private static final String TAG = "DataRowHandlerForPhoto";
private final PhotoStore mPhotoStore;
private final int mMaxDisplayPhotoDim;
private final int mMaxThumbnailPhotoDim;
/**
* If this is set in the ContentValues passed in, it indicates that the caller has
* already taken care of photo processing, and that the row should be ready for
* insert/update. This is used when the photo has been written directly to an
* asset file.
*/
/* package */ static final String SKIP_PROCESSING_KEY = "skip_processing";
public DataRowHandlerForPhoto(
Context context, ContactsDatabaseHelper dbHelper, AbstractContactAggregator aggregator,
PhotoStore photoStore, int maxDisplayPhotoDim, int maxThumbnailPhotoDim) {
super(context, dbHelper, aggregator, Photo.CONTENT_ITEM_TYPE);
mPhotoStore = photoStore;
mMaxDisplayPhotoDim = maxDisplayPhotoDim;
mMaxThumbnailPhotoDim = maxThumbnailPhotoDim;
}
@Override
public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
ContentValues values) {
if (values.containsKey(SKIP_PROCESSING_KEY)) {
values.remove(SKIP_PROCESSING_KEY);
} else {
// Pre-process the photo if one exists.
if (!preProcessPhoto(values)) {
return 0;
}
}
long dataId = super.insert(db, txContext, rawContactId, values);
if (!txContext.isNewRawContact(rawContactId)) {
mContactAggregator.updatePhotoId(db, rawContactId);
}
return dataId;
}
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
if (values.containsKey(SKIP_PROCESSING_KEY)) {
values.remove(SKIP_PROCESSING_KEY);
} else {
// Pre-process the photo if one exists.
if (!preProcessPhoto(values)) {
return false;
}
}
// Do the actual update.
if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) {
return false;
}
mContactAggregator.updatePhotoId(db, rawContactId);
return true;
}
/**
* Pre-processes the given content values for update or insert. If the photo column contains
* null or an empty byte array, both that column and the photo file ID will be nulled out.
* If a photo was specified but could not be processed, this will return false.
* @param values The content values passed in.
* @return Whether processing was successful - on failure, the operation should abort.
*/
private boolean preProcessPhoto(ContentValues values) {
if (values.containsKey(Photo.PHOTO)) {
boolean photoExists = hasNonNullPhoto(values);
if (photoExists) {
if (!processPhoto(values)) {
// A photo was passed in, but we couldn't process it. Update failed.
return false;
}
} else {
// The photo key was passed in, but it was either null or an empty byte[].
// We should set the photo and photo file ID fields to null for the update.
values.putNull(Photo.PHOTO);
values.putNull(Photo.PHOTO_FILE_ID);
}
}
return true;
}
private boolean hasNonNullPhoto(ContentValues values) {
byte[] photoBytes = values.getAsByteArray(Photo.PHOTO);
return photoBytes != null && photoBytes.length > 0;
}
@Override
public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) {
long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
int count = super.delete(db, txContext, c);
mContactAggregator.updatePhotoId(db, rawContactId);
return count;
}
/**
* Reads the photo out of the given values object and processes it, placing the processed
* photos (a photo store file ID and a compressed thumbnail) back into the ContentValues
* object.
* @param values The values being inserted or updated - assumed to contain a photo BLOB.
* @return Whether an image was successfully decoded and processed.
*/
private boolean processPhoto(ContentValues values) {
byte[] originalPhoto = values.getAsByteArray(Photo.PHOTO);
if (originalPhoto != null) {
try {
PhotoProcessor processor = new PhotoProcessor(
originalPhoto, mMaxDisplayPhotoDim, mMaxThumbnailPhotoDim);
long photoFileId = mPhotoStore.insert(processor);
if (photoFileId != 0) {
values.put(Photo.PHOTO_FILE_ID, photoFileId);
} else {
values.putNull(Photo.PHOTO_FILE_ID);
}
values.put(Photo.PHOTO, processor.getThumbnailPhotoBytes());
return true;
} catch (IOException ioe) {
Log.e(TAG, "Could not process photo for insert or update", ioe);
}
}
return false;
}
}