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.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