Home | History | Annotate | Download | only in contacts
      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 
     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
     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
     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      */
    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 
    125     private boolean hasNonNullPhoto(ContentValues values) {
    126         byte[] photoBytes = values.getAsByteArray(Photo.PHOTO);
    127         return photoBytes != null && photoBytes.length > 0;
    128     }
    129 
    130     @Override
    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      */
    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