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.content.res.Resources; 21 import android.database.Cursor; 22 import android.database.sqlite.SQLiteDatabase; 23 import android.provider.ContactsContract.Data; 24 import android.provider.ContactsContract.CommonDataKinds.Photo; 25 import android.util.Log; 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 38 /** 39 * If this is set in the ContentValues passed in, it indicates that the caller has 40 * already taken care of photo processing, and that the row should be ready for 41 * insert/update. This is used when the photo has been written directly to an 42 * asset file. 43 */ 44 /* package */ static final String SKIP_PROCESSING_KEY = "skip_processing"; 45 46 public DataRowHandlerForPhoto( 47 Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, 48 PhotoStore photoStore) { 49 super(context, dbHelper, aggregator, Photo.CONTENT_ITEM_TYPE); 50 mPhotoStore = photoStore; 51 } 52 53 @Override 54 public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId, 55 ContentValues values) { 56 57 if (values.containsKey(SKIP_PROCESSING_KEY)) { 58 values.remove(SKIP_PROCESSING_KEY); 59 } else { 60 // Pre-process the photo if one exists. 61 if (!preProcessPhoto(values)) { 62 return 0; 63 } 64 } 65 66 long dataId = super.insert(db, txContext, rawContactId, values); 67 if (!txContext.isNewRawContact(rawContactId)) { 68 mContactAggregator.updatePhotoId(db, rawContactId); 69 } 70 return dataId; 71 } 72 73 @Override 74 public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values, 75 Cursor c, boolean callerIsSyncAdapter) { 76 long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID); 77 78 if (values.containsKey(SKIP_PROCESSING_KEY)) { 79 values.remove(SKIP_PROCESSING_KEY); 80 } else { 81 // Pre-process the photo if one exists. 82 if (!preProcessPhoto(values)) { 83 return false; 84 } 85 } 86 87 // Do the actual update. 88 if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) { 89 return false; 90 } 91 92 mContactAggregator.updatePhotoId(db, rawContactId); 93 return true; 94 } 95 96 /** 97 * Pre-processes the given content values for update or insert. If the photo column contains 98 * null or an empty byte array, both that column and the photo file ID will be nulled out. 99 * If a photo was specified but could not be processed, this will return false. 100 * @param values The content values passed in. 101 * @return Whether processing was successful - on failure, the operation should abort. 102 */ 103 private boolean preProcessPhoto(ContentValues values) { 104 if (values.containsKey(Photo.PHOTO)) { 105 boolean photoExists = hasNonNullPhoto(values); 106 if (photoExists) { 107 if (!processPhoto(values)) { 108 // A photo was passed in, but we couldn't process it. Update failed. 109 return false; 110 } 111 } else { 112 // The photo key was passed in, but it was either null or an empty byte[]. 113 // We should set the photo and photo file ID fields to null for the update. 114 values.putNull(Photo.PHOTO); 115 values.putNull(Photo.PHOTO_FILE_ID); 116 } 117 } 118 return true; 119 } 120 121 private boolean hasNonNullPhoto(ContentValues values) { 122 byte[] photoBytes = values.getAsByteArray(Photo.PHOTO); 123 return photoBytes != null && photoBytes.length > 0; 124 } 125 126 @Override 127 public int delete(SQLiteDatabase db, TransactionContext txContext, Cursor c) { 128 long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID); 129 int count = super.delete(db, txContext, c); 130 mContactAggregator.updatePhotoId(db, rawContactId); 131 return count; 132 } 133 134 /** 135 * Reads the photo out of the given values object and processes it, placing the processed 136 * photos (a photo store file ID and a compressed thumbnail) back into the ContentValues 137 * object. 138 * @param values The values being inserted or updated - assumed to contain a photo BLOB. 139 * @return Whether an image was successfully decoded and processed. 140 */ 141 private boolean processPhoto(ContentValues values) { 142 byte[] originalPhoto = values.getAsByteArray(Photo.PHOTO); 143 if (originalPhoto != null) { 144 int maxDisplayPhotoDim = mContext.getResources().getInteger( 145 R.integer.config_max_display_photo_dim); 146 int maxThumbnailPhotoDim = mContext.getResources().getInteger( 147 R.integer.config_max_thumbnail_photo_dim); 148 try { 149 PhotoProcessor processor = new PhotoProcessor( 150 originalPhoto, maxDisplayPhotoDim, maxThumbnailPhotoDim); 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