1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License 15 */ 16 package com.android.providers.contacts; 17 18 import android.content.ContentValues; 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.database.sqlite.SQLiteDatabase; 22 import android.provider.ContactsContract.CommonDataKinds.Photo; 23 import android.util.Log; 24 25 import com.android.providers.contacts.aggregation.AbstractContactAggregator; 26 27 import java.io.IOException; 28 29 /** 30 * Handler for photo data rows. 31 */ 32 public class DataRowHandlerForPhoto extends DataRowHandler { 33 34 private static final String TAG = "DataRowHandlerForPhoto"; 35 36 private final PhotoStore mPhotoStore; 37 private final int mMaxDisplayPhotoDim; 38 private final int mMaxThumbnailPhotoDim; 39 40 /** 41 * If this is set in the ContentValues passed in, it indicates that the caller has 42 * already taken care of photo processing, and that the row should be ready for 43 * insert/update. This is used when the photo has been written directly to an 44 * asset file. 45 */ 46 /* package */ static final String SKIP_PROCESSING_KEY = "skip_processing"; 47 DataRowHandlerForPhoto( Context context, ContactsDatabaseHelper dbHelper, AbstractContactAggregator aggregator, PhotoStore photoStore, int maxDisplayPhotoDim, int maxThumbnailPhotoDim)48 public DataRowHandlerForPhoto( 49 Context context, ContactsDatabaseHelper dbHelper, AbstractContactAggregator aggregator, 50 PhotoStore photoStore, int maxDisplayPhotoDim, int maxThumbnailPhotoDim) { 51 super(context, dbHelper, aggregator, Photo.CONTENT_ITEM_TYPE); 52 mPhotoStore = photoStore; 53 mMaxDisplayPhotoDim = maxDisplayPhotoDim; 54 mMaxThumbnailPhotoDim = maxThumbnailPhotoDim; 55 } 56 57 @Override insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, ContentValues values)58 public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, 59 ContentValues values) { 60 61 if (values.containsKey(SKIP_PROCESSING_KEY)) { 62 values.remove(SKIP_PROCESSING_KEY); 63 } else { 64 // Pre-process the photo if one exists. 65 if (!preProcessPhoto(values)) { 66 return 0; 67 } 68 } 69 70 long dataId = super.insert(db, txContext, rawContactId, values); 71 if (!txContext.isNewRawContact(rawContactId)) { 72 mContactAggregator.updatePhotoId(db, rawContactId); 73 } 74 return dataId; 75 } 76 77 @Override update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter)78 public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, 79 Cursor c, boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) { 80 long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); 81 82 if (values.containsKey(SKIP_PROCESSING_KEY)) { 83 values.remove(SKIP_PROCESSING_KEY); 84 } else { 85 // Pre-process the photo if one exists. 86 if (!preProcessPhoto(values)) { 87 return false; 88 } 89 } 90 91 // Do the actual update. 92 if (!super.update(db, txContext, values, c, callerIsSyncAdapter, callerIsMetadataSyncAdapter)) { 93 return false; 94 } 95 96 mContactAggregator.updatePhotoId(db, rawContactId); 97 return true; 98 } 99 100 /** 101 * Pre-processes the given content values for update or insert. If the photo column contains 102 * null or an empty byte array, both that column and the photo file ID will be nulled out. 103 * If a photo was specified but could not be processed, this will return false. 104 * @param values The content values passed in. 105 * @return Whether processing was successful - on failure, the operation should abort. 106 */ preProcessPhoto(ContentValues values)107 private boolean preProcessPhoto(ContentValues values) { 108 if (values.containsKey(Photo.PHOTO)) { 109 boolean photoExists = hasNonNullPhoto(values); 110 if (photoExists) { 111 if (!processPhoto(values)) { 112 // A photo was passed in, but we couldn't process it. Update failed. 113 return false; 114 } 115 } else { 116 // The photo key was passed in, but it was either null or an empty byte[]. 117 // We should set the photo and photo file ID fields to null for the update. 118 values.putNull(Photo.PHOTO); 119 values.putNull(Photo.PHOTO_FILE_ID); 120 } 121 } 122 return true; 123 } 124 hasNonNullPhoto(ContentValues values)125 private boolean hasNonNullPhoto(ContentValues values) { 126 byte[] photoBytes = values.getAsByteArray(Photo.PHOTO); 127 return photoBytes != null && photoBytes.length > 0; 128 } 129 130 @Override delete(SQLiteDatabase db, TransactionContext txContext, Cursor c)131 public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { 132 long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); 133 int count = super.delete(db, txContext, c); 134 mContactAggregator.updatePhotoId(db, rawContactId); 135 return count; 136 } 137 138 /** 139 * Reads the photo out of the given values object and processes it, placing the processed 140 * photos (a photo store file ID and a compressed thumbnail) back into the ContentValues 141 * object. 142 * @param values The values being inserted or updated - assumed to contain a photo BLOB. 143 * @return Whether an image was successfully decoded and processed. 144 */ processPhoto(ContentValues values)145 private boolean processPhoto(ContentValues values) { 146 byte[] originalPhoto = values.getAsByteArray(Photo.PHOTO); 147 if (originalPhoto != null) { 148 try { 149 PhotoProcessor processor = new PhotoProcessor( 150 originalPhoto, mMaxDisplayPhotoDim, mMaxThumbnailPhotoDim); 151 long photoFileId = mPhotoStore.insert(processor); 152 if (photoFileId != 0) { 153 values.put(Photo.PHOTO_FILE_ID, photoFileId); 154 } else { 155 values.putNull(Photo.PHOTO_FILE_ID); 156 } 157 values.put(Photo.PHOTO, processor.getThumbnailPhotoBytes()); 158 return true; 159 } catch (IOException ioe) { 160 Log.e(TAG, "Could not process photo for insert or update", ioe); 161 } 162 } 163 return false; 164 } 165 } 166