| /* |
| * 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; |
| } |
| } |