Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts;
     18 
     19 import com.google.android.collect.Maps;
     20 
     21 import android.app.Activity;
     22 import android.content.ContentProviderOperation;
     23 import android.content.ContentResolver;
     24 import android.content.ContentUris;
     25 import android.content.ContentValues;
     26 import android.content.Intent;
     27 import android.content.OperationApplicationException;
     28 import android.database.Cursor;
     29 import android.graphics.Bitmap;
     30 import android.net.Uri;
     31 import android.os.Bundle;
     32 import android.os.RemoteException;
     33 import android.provider.ContactsContract;
     34 import android.provider.ContactsContract.Contacts;
     35 import android.provider.ContactsContract.RawContacts;
     36 import android.provider.ContactsContract.CommonDataKinds.Photo;
     37 import android.widget.Toast;
     38 
     39 import com.android.contacts.model.ExchangeSource;
     40 import com.android.contacts.model.GoogleSource;
     41 
     42 import java.io.ByteArrayOutputStream;
     43 import java.util.ArrayList;
     44 import java.util.HashMap;
     45 
     46 /**
     47  * Provides an external interface for other applications to attach images
     48  * to contacts. It will first present a contact picker and then run the
     49  * image that is handed to it through the cropper to make the image the proper
     50  * size and give the user a chance to use the face detector.
     51  */
     52 public class AttachImage extends Activity {
     53     private static final int REQUEST_PICK_CONTACT = 1;
     54     private static final int REQUEST_CROP_PHOTO = 2;
     55 
     56     private static final String RAW_CONTACT_URIS_KEY = "raw_contact_uris";
     57 
     58     public AttachImage() {
     59 
     60     }
     61 
     62     private Long[] mRawContactIds;
     63 
     64     private ContentResolver mContentResolver;
     65 
     66     @Override
     67     public void onCreate(Bundle icicle) {
     68         super.onCreate(icicle);
     69 
     70         if (icicle != null) {
     71             mRawContactIds = toClassArray(icicle.getLongArray(RAW_CONTACT_URIS_KEY));
     72         } else {
     73             Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
     74             intent.setType(Contacts.CONTENT_ITEM_TYPE);
     75             startActivityForResult(intent, REQUEST_PICK_CONTACT);
     76         }
     77 
     78         mContentResolver = getContentResolver();
     79     }
     80 
     81     @Override
     82     protected void onSaveInstanceState(Bundle outState) {
     83         super.onSaveInstanceState(outState);
     84 
     85         if (mRawContactIds != null && mRawContactIds.length != 0) {
     86             outState.putLongArray(RAW_CONTACT_URIS_KEY, toPrimativeArray(mRawContactIds));
     87         }
     88     }
     89 
     90     private static long[] toPrimativeArray(Long[] in) {
     91         if (in == null) {
     92             return null;
     93         }
     94         long[] out = new long[in.length];
     95         for (int i = 0; i < in.length; i++) {
     96             out[i] = in[i];
     97         }
     98         return out;
     99     }
    100 
    101     private static Long[] toClassArray(long[] in) {
    102         if (in == null) {
    103             return null;
    104         }
    105         Long[] out = new Long[in.length];
    106         for (int i = 0; i < in.length; i++) {
    107             out[i] = in[i];
    108         }
    109         return out;
    110     }
    111 
    112     @Override
    113     protected void onActivityResult(int requestCode, int resultCode, Intent result) {
    114         if (resultCode != RESULT_OK) {
    115             finish();
    116             return;
    117         }
    118 
    119         if (requestCode == REQUEST_PICK_CONTACT) {
    120             // A contact was picked. Launch the cropper to get face detection, the right size, etc.
    121             // TODO: get these values from constants somewhere
    122             Intent myIntent = getIntent();
    123             Intent intent = new Intent("com.android.camera.action.CROP", myIntent.getData());
    124             if (myIntent.getStringExtra("mimeType") != null) {
    125                 intent.setDataAndType(myIntent.getData(), myIntent.getStringExtra("mimeType"));
    126             }
    127             intent.putExtra("crop", "true");
    128             intent.putExtra("aspectX", 1);
    129             intent.putExtra("aspectY", 1);
    130             intent.putExtra("outputX", 96);
    131             intent.putExtra("outputY", 96);
    132             intent.putExtra("return-data", true);
    133             startActivityForResult(intent, REQUEST_CROP_PHOTO);
    134 
    135             // while they're cropping, convert the contact into a raw_contact
    136             final long contactId = ContentUris.parseId(result.getData());
    137             final ArrayList<Long> rawContactIdsList = ContactsUtils.queryForAllRawContactIds(
    138                     mContentResolver, contactId);
    139             mRawContactIds = new Long[rawContactIdsList.size()];
    140             mRawContactIds = rawContactIdsList.toArray(mRawContactIds);
    141 
    142             if (mRawContactIds == null || rawContactIdsList.isEmpty()) {
    143                 Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
    144             }
    145         } else if (requestCode == REQUEST_CROP_PHOTO) {
    146             final Bundle extras = result.getExtras();
    147             if (extras != null && mRawContactIds != null) {
    148                 Bitmap photo = extras.getParcelable("data");
    149                 if (photo != null) {
    150                     ByteArrayOutputStream stream = new ByteArrayOutputStream();
    151                     photo.compress(Bitmap.CompressFormat.JPEG, 75, stream);
    152 
    153                     final ContentValues imageValues = new ContentValues();
    154                     imageValues.put(Photo.PHOTO, stream.toByteArray());
    155                     imageValues.put(RawContacts.Data.IS_SUPER_PRIMARY, 1);
    156 
    157                     // attach the photo to every raw contact
    158                     for (Long rawContactId : mRawContactIds) {
    159 
    160                         // exchange and google only allow one image, so do an update rather than insert
    161                         boolean shouldUpdate = false;
    162 
    163                         final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
    164                                 rawContactId);
    165                         final Uri rawContactDataUri = Uri.withAppendedPath(rawContactUri,
    166                                 RawContacts.Data.CONTENT_DIRECTORY);
    167                         insertPhoto(imageValues, rawContactDataUri, true);
    168                     }
    169                 }
    170             }
    171             finish();
    172         }
    173     }
    174 
    175     /**
    176      * Inserts a photo on the raw contact.
    177      * @param values the photo values
    178      * @param assertAccount if true, will check to verify if the account is Google or exchange,
    179      *     no photos exist (Google and exchange only take one picture)
    180      */
    181     private void insertPhoto(ContentValues values, Uri rawContactDataUri,
    182             boolean assertAccount) {
    183 
    184         ArrayList<ContentProviderOperation> operations =
    185             new ArrayList<ContentProviderOperation>();
    186 
    187         if (assertAccount) {
    188             // make sure for Google and exchange, no pictures exist
    189             operations.add(ContentProviderOperation.newAssertQuery(rawContactDataUri)
    190                     .withSelection(Photo.MIMETYPE + "=? AND "
    191                             + RawContacts.ACCOUNT_TYPE + " IN (?,?)",
    192                             new String[] {Photo.CONTENT_ITEM_TYPE, GoogleSource.ACCOUNT_TYPE,
    193                             ExchangeSource.ACCOUNT_TYPE})
    194                             .withExpectedCount(0).build());
    195         }
    196 
    197         // insert the photo
    198         values.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
    199         operations.add(ContentProviderOperation.newInsert(rawContactDataUri)
    200                 .withValues(values).build());
    201 
    202         try {
    203             mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
    204         } catch (RemoteException e) {
    205             throw new IllegalStateException("Problem querying raw_contacts/data", e);
    206         } catch (OperationApplicationException e) {
    207             // the account doesn't allow multiple photos, so update
    208             if (assertAccount) {
    209                 updatePhoto(values, rawContactDataUri, false);
    210             } else {
    211                 throw new IllegalStateException("Problem inserting photo into raw_contacts/data", e);
    212             }
    213         }
    214     }
    215 
    216     /**
    217      * Tries to update the photo on the raw_contact.  If no photo exists, and allowInsert == true,
    218      * then will try to {@link #updatePhoto(ContentValues, boolean)}
    219      */
    220     private void updatePhoto(ContentValues values, Uri rawContactDataUri,
    221             boolean allowInsert) {
    222         ArrayList<ContentProviderOperation> operations =
    223             new ArrayList<ContentProviderOperation>();
    224 
    225         values.remove(Photo.MIMETYPE);
    226 
    227         // check that a photo exists
    228         operations.add(ContentProviderOperation.newAssertQuery(rawContactDataUri)
    229                 .withSelection(Photo.MIMETYPE + "=?", new String[] {
    230                     Photo.CONTENT_ITEM_TYPE
    231                 }).withExpectedCount(1).build());
    232 
    233         // update that photo
    234         operations.add(ContentProviderOperation.newUpdate(rawContactDataUri).withSelection(Photo.MIMETYPE + "=?", new String[] {
    235                     Photo.CONTENT_ITEM_TYPE}).withValues(values).build());
    236 
    237         try {
    238             mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
    239         } catch (RemoteException e) {
    240             throw new IllegalStateException("Problem querying raw_contacts/data", e);
    241         } catch (OperationApplicationException e) {
    242             if (allowInsert) {
    243                 // they deleted the photo between insert and update, so insert one
    244                 insertPhoto(values, rawContactDataUri, false);
    245             } else {
    246                 throw new IllegalStateException("Problem inserting photo raw_contacts/data", e);
    247             }
    248         }
    249     }
    250 }
    251