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.activities; 18 19 import android.content.ContentResolver; 20 import android.content.Intent; 21 import android.content.Loader; 22 import android.content.Loader.OnLoadCompleteListener; 23 import android.database.Cursor; 24 import android.graphics.Bitmap; 25 import android.graphics.BitmapFactory; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.provider.ContactsContract.CommonDataKinds.Photo; 29 import android.provider.ContactsContract.Contacts; 30 import android.provider.ContactsContract.DisplayPhoto; 31 import android.util.Log; 32 33 import com.android.contacts.ContactLoader; 34 import com.android.contacts.ContactLoader.Result; 35 import com.android.contacts.ContactSaveService; 36 import com.android.contacts.ContactsActivity; 37 import com.android.contacts.ContactsUtils; 38 import com.android.contacts.model.AccountType; 39 import com.android.contacts.model.EntityDelta; 40 import com.android.contacts.model.EntityDeltaList; 41 import com.android.contacts.model.EntityModifier; 42 import com.android.contacts.util.ContactPhotoUtils; 43 44 import java.io.File; 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 AttachPhotoActivity extends ContactsActivity { 53 private static final String TAG = AttachPhotoActivity.class.getSimpleName(); 54 55 private static final int REQUEST_PICK_CONTACT = 1; 56 private static final int REQUEST_CROP_PHOTO = 2; 57 58 private static final String KEY_CONTACT_URI = "contact_uri"; 59 private static final String KEY_TEMP_PHOTO_URI = "temp_photo_uri"; 60 61 private File mTempPhotoFile; 62 private Uri mTempPhotoUri; 63 64 private ContentResolver mContentResolver; 65 66 // Height and width (in pixels) to request for the photo - queried from the provider. 67 private static int mPhotoDim; 68 69 private Uri mContactUri; 70 71 @Override 72 public void onCreate(Bundle icicle) { 73 super.onCreate(icicle); 74 75 if (icicle != null) { 76 final String uri = icicle.getString(KEY_CONTACT_URI); 77 mContactUri = (uri == null) ? null : Uri.parse(uri); 78 79 mTempPhotoUri = Uri.parse(icicle.getString(KEY_TEMP_PHOTO_URI)); 80 mTempPhotoFile = new File(mTempPhotoUri.getPath()); 81 } else { 82 mTempPhotoFile = ContactPhotoUtils.generateTempPhotoFile(this); 83 mTempPhotoUri = Uri.fromFile(mTempPhotoFile); 84 85 Intent intent = new Intent(Intent.ACTION_PICK); 86 intent.setType(Contacts.CONTENT_TYPE); 87 startActivityForResult(intent, REQUEST_PICK_CONTACT); 88 } 89 90 mContentResolver = getContentResolver(); 91 92 // Load the photo dimension to request. 93 Cursor c = mContentResolver.query(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, 94 new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null); 95 try { 96 c.moveToFirst(); 97 mPhotoDim = c.getInt(0); 98 } finally { 99 c.close(); 100 } 101 } 102 103 @Override 104 protected void onSaveInstanceState(Bundle outState) { 105 super.onSaveInstanceState(outState); 106 if (mContactUri != null) outState.putString(KEY_CONTACT_URI, mContactUri.toString()); 107 outState.putString(KEY_TEMP_PHOTO_URI, mTempPhotoUri.toString()); 108 } 109 110 @Override 111 protected void onActivityResult(int requestCode, int resultCode, Intent result) { 112 if (resultCode != RESULT_OK) { 113 finish(); 114 return; 115 } 116 117 if (requestCode == REQUEST_PICK_CONTACT) { 118 // A contact was picked. Launch the cropper to get face detection, the right size, etc. 119 // TODO: get these values from constants somewhere 120 Intent myIntent = getIntent(); 121 Intent intent = new Intent("com.android.camera.action.CROP", myIntent.getData()); 122 if (myIntent.getStringExtra("mimeType") != null) { 123 intent.setDataAndType(myIntent.getData(), myIntent.getStringExtra("mimeType")); 124 } 125 ContactPhotoUtils.addGalleryIntentExtras(intent, mTempPhotoUri, mPhotoDim); 126 127 startActivityForResult(intent, REQUEST_CROP_PHOTO); 128 129 mContactUri = result.getData(); 130 131 } else if (requestCode == REQUEST_CROP_PHOTO) { 132 loadContact(mContactUri, new Listener() { 133 @Override 134 public void onContactLoaded(ContactLoader.Result contact) { 135 saveContact(contact); 136 } 137 }); 138 } 139 } 140 141 // TODO: consider moving this to ContactLoader, especially if we keep adding similar 142 // code elsewhere (ViewNotificationService is another case). The only concern is that, 143 // although this is convenient, it isn't quite as robust as using LoaderManager... for 144 // instance, the loader doesn't persist across Activity restarts. 145 private void loadContact(Uri contactUri, final Listener listener) { 146 final ContactLoader loader = new ContactLoader(this, contactUri, true); 147 loader.registerListener(0, new OnLoadCompleteListener<ContactLoader.Result>() { 148 @Override 149 public void onLoadComplete( 150 Loader<ContactLoader.Result> loader, ContactLoader.Result contact) { 151 try { 152 loader.reset(); 153 } 154 catch (RuntimeException e) { 155 Log.e(TAG, "Error resetting loader", e); 156 } 157 listener.onContactLoaded(contact); 158 } 159 }); 160 loader.startLoading(); 161 } 162 163 private interface Listener { 164 public void onContactLoaded(Result contact); 165 } 166 167 /** 168 * If prerequisites have been met, attach the photo to a raw-contact and save. 169 * The prerequisites are: 170 * - photo has been cropped 171 * - contact has been loaded 172 */ 173 private void saveContact(ContactLoader.Result contact) { 174 175 // Obtain the raw-contact that we will save to. 176 EntityDeltaList deltaList = contact.createEntityDeltaList(); 177 EntityDelta raw = deltaList.getFirstWritableRawContact(this); 178 if (raw == null) { 179 Log.w(TAG, "no writable raw-contact found"); 180 return; 181 } 182 183 // Create a scaled, compressed bitmap to add to the entity-delta list. 184 final int size = ContactsUtils.getThumbnailSize(this); 185 final Bitmap bitmap = BitmapFactory.decodeFile(mTempPhotoFile.getAbsolutePath()); 186 final Bitmap scaled = Bitmap.createScaledBitmap(bitmap, size, size, false); 187 final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled); 188 if (compressed == null) { 189 Log.w(TAG, "could not create scaled and compressed Bitmap"); 190 return; 191 } 192 193 // Add compressed bitmap to entity-delta... this allows us to save to 194 // a new contact; otherwise the entity-delta-list would be empty, and 195 // the ContactSaveService would not create the new contact, and the 196 // full-res photo would fail to be saved to the non-existent contact. 197 AccountType account = raw.getRawContactAccountType(this); 198 EntityDelta.ValuesDelta values = 199 EntityModifier.ensureKindExists(raw, account, Photo.CONTENT_ITEM_TYPE); 200 if (values == null) { 201 Log.w(TAG, "cannot attach photo to this account type"); 202 return; 203 } 204 values.put(Photo.PHOTO, compressed); 205 206 // Finally, invoke the ContactSaveService. 207 Log.v(TAG, "all prerequisites met, about to save photo to contact"); 208 Intent intent = ContactSaveService.createSaveContactIntent( 209 this, 210 deltaList, 211 "", 0, 212 contact.isUserProfile(), 213 null, null, 214 raw.getRawContactId(), 215 mTempPhotoFile.getAbsolutePath()); 216 startService(intent); 217 finish(); 218 } 219 } 220