AttachPhotoActivity.java revision adeadcf055fee765df22c479fc0afc9a3e4d5e66
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 17package com.android.contacts.activities; 18 19import android.content.ContentResolver; 20import android.content.Intent; 21import android.content.Loader; 22import android.content.Loader.OnLoadCompleteListener; 23import android.content.pm.PackageManager; 24import android.database.Cursor; 25import android.graphics.Bitmap; 26import android.graphics.BitmapFactory; 27import android.net.Uri; 28import android.os.Bundle; 29import android.provider.ContactsContract.CommonDataKinds.Photo; 30import android.provider.ContactsContract.Contacts; 31import android.provider.ContactsContract.DisplayPhoto; 32import android.util.Log; 33 34import com.android.contacts.ContactSaveService; 35import com.android.contacts.ContactsActivity; 36import com.android.contacts.ContactsUtils; 37import com.android.contacts.model.Contact; 38import com.android.contacts.model.ContactLoader; 39import com.android.contacts.model.RawContactDelta; 40import com.android.contacts.model.RawContactDeltaList; 41import com.android.contacts.model.RawContactModifier; 42import com.android.contacts.common.model.account.AccountType; 43import com.android.contacts.common.model.ValuesDelta; 44import com.android.contacts.util.ContactPhotoUtils; 45 46import java.io.File; 47import java.io.FileNotFoundException; 48 49/** 50 * Provides an external interface for other applications to attach images 51 * to contacts. It will first present a contact picker and then run the 52 * image that is handed to it through the cropper to make the image the proper 53 * size and give the user a chance to use the face detector. 54 */ 55public class AttachPhotoActivity extends ContactsActivity { 56 private static final String TAG = AttachPhotoActivity.class.getSimpleName(); 57 58 private static final int REQUEST_PICK_CONTACT = 1; 59 private static final int REQUEST_CROP_PHOTO = 2; 60 61 private static final String KEY_CONTACT_URI = "contact_uri"; 62 private static final String KEY_TEMP_PHOTO_URI = "temp_photo_uri"; 63 private static final String KEY_CROPPED_PHOTO_URI = "cropped_photo_uri"; 64 65 private Uri mTempPhotoUri; 66 private Uri mCroppedPhotoUri; 67 68 private ContentResolver mContentResolver; 69 70 // Height and width (in pixels) to request for the photo - queried from the provider. 71 private static int mPhotoDim; 72 73 private Uri mContactUri; 74 75 @Override 76 public void onCreate(Bundle icicle) { 77 super.onCreate(icicle); 78 79 if (icicle != null) { 80 final String uri = icicle.getString(KEY_CONTACT_URI); 81 mContactUri = (uri == null) ? null : Uri.parse(uri); 82 mTempPhotoUri = Uri.parse(icicle.getString(KEY_TEMP_PHOTO_URI)); 83 mCroppedPhotoUri = Uri.parse(icicle.getString(KEY_CROPPED_PHOTO_URI)); 84 } else { 85 mTempPhotoUri = ContactPhotoUtils.generateTempImageUri(this); 86 mCroppedPhotoUri = ContactPhotoUtils.generateTempCroppedImageUri(this); 87 Intent intent = new Intent(Intent.ACTION_PICK); 88 intent.setType(Contacts.CONTENT_TYPE); 89 startActivityForResult(intent, REQUEST_PICK_CONTACT); 90 } 91 92 mContentResolver = getContentResolver(); 93 94 // Load the photo dimension to request. 95 Cursor c = mContentResolver.query(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, 96 new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null); 97 try { 98 c.moveToFirst(); 99 mPhotoDim = c.getInt(0); 100 } finally { 101 c.close(); 102 } 103 } 104 105 @Override 106 protected void onSaveInstanceState(Bundle outState) { 107 super.onSaveInstanceState(outState); 108 if (mContactUri != null) { 109 outState.putString(KEY_CONTACT_URI, mContactUri.toString()); 110 } 111 if (mTempPhotoUri != null) { 112 outState.putString(KEY_TEMP_PHOTO_URI, mTempPhotoUri.toString()); 113 } 114 } 115 116 @Override 117 protected void onActivityResult(int requestCode, int resultCode, Intent result) { 118 if (requestCode == REQUEST_PICK_CONTACT) { 119 if (resultCode != RESULT_OK) { 120 finish(); 121 return; 122 } 123 // A contact was picked. Launch the cropper to get face detection, the right size, etc. 124 // TODO: get these values from constants somewhere 125 final Intent myIntent = getIntent(); 126 final Uri inputUri = myIntent.getData(); 127 128 final int perm = checkUriPermission(inputUri, android.os.Process.myPid(), 129 android.os.Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION | 130 Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 131 132 final Uri toCrop; 133 134 if (perm == PackageManager.PERMISSION_DENIED) { 135 // Work around to save a read-only URI into a temporary file provider URI so that 136 // we can add the FLAG_GRANT_WRITE_URI_PERMISSION flag to the eventual 137 // crop intent b/10837468 138 ContactPhotoUtils.savePhotoFromUriToUri(this, inputUri, mTempPhotoUri, false); 139 toCrop = mTempPhotoUri; 140 } else { 141 toCrop = inputUri; 142 } 143 144 final Intent intent = new Intent("com.android.camera.action.CROP", toCrop); 145 if (myIntent.getStringExtra("mimeType") != null) { 146 intent.setDataAndType(toCrop, myIntent.getStringExtra("mimeType")); 147 } 148 ContactPhotoUtils.addPhotoPickerExtras(intent, mCroppedPhotoUri); 149 ContactPhotoUtils.addCropExtras(intent, mPhotoDim); 150 151 startActivityForResult(intent, REQUEST_CROP_PHOTO); 152 153 mContactUri = result.getData(); 154 155 } else if (requestCode == REQUEST_CROP_PHOTO) { 156 // Delete the temporary photo from cache now that we have a cropped version. 157 // We should do this even if the crop failed and we eventually bail 158 getContentResolver().delete(mTempPhotoUri, null, null); 159 if (resultCode != RESULT_OK) { 160 finish(); 161 return; 162 } 163 loadContact(mContactUri, new Listener() { 164 @Override 165 public void onContactLoaded(Contact contact) { 166 saveContact(contact); 167 } 168 }); 169 } 170 } 171 172 // TODO: consider moving this to ContactLoader, especially if we keep adding similar 173 // code elsewhere (ViewNotificationService is another case). The only concern is that, 174 // although this is convenient, it isn't quite as robust as using LoaderManager... for 175 // instance, the loader doesn't persist across Activity restarts. 176 private void loadContact(Uri contactUri, final Listener listener) { 177 final ContactLoader loader = new ContactLoader(this, contactUri, true); 178 loader.registerListener(0, new OnLoadCompleteListener<Contact>() { 179 @Override 180 public void onLoadComplete( 181 Loader<Contact> loader, Contact contact) { 182 try { 183 loader.reset(); 184 } 185 catch (RuntimeException e) { 186 Log.e(TAG, "Error resetting loader", e); 187 } 188 listener.onContactLoaded(contact); 189 } 190 }); 191 loader.startLoading(); 192 } 193 194 private interface Listener { 195 public void onContactLoaded(Contact contact); 196 } 197 198 /** 199 * If prerequisites have been met, attach the photo to a raw-contact and save. 200 * The prerequisites are: 201 * - photo has been cropped 202 * - contact has been loaded 203 */ 204 private void saveContact(Contact contact) { 205 206 // Obtain the raw-contact that we will save to. 207 RawContactDeltaList deltaList = contact.createRawContactDeltaList(); 208 RawContactDelta raw = deltaList.getFirstWritableRawContact(this); 209 if (raw == null) { 210 Log.w(TAG, "no writable raw-contact found"); 211 return; 212 } 213 214 // Create a scaled, compressed bitmap to add to the entity-delta list. 215 final int size = ContactsUtils.getThumbnailSize(this); 216 Bitmap bitmap; 217 try { 218 bitmap = ContactPhotoUtils.getBitmapFromUri(this, mCroppedPhotoUri); 219 } catch (FileNotFoundException e) { 220 Log.w(TAG, "Could not find bitmap"); 221 return; 222 } 223 224 final Bitmap scaled = Bitmap.createScaledBitmap(bitmap, size, size, false); 225 final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled); 226 if (compressed == null) { 227 Log.w(TAG, "could not create scaled and compressed Bitmap"); 228 return; 229 } 230 // Add compressed bitmap to entity-delta... this allows us to save to 231 // a new contact; otherwise the entity-delta-list would be empty, and 232 // the ContactSaveService would not create the new contact, and the 233 // full-res photo would fail to be saved to the non-existent contact. 234 AccountType account = raw.getRawContactAccountType(this); 235 ValuesDelta values = 236 RawContactModifier.ensureKindExists(raw, account, Photo.CONTENT_ITEM_TYPE); 237 if (values == null) { 238 Log.w(TAG, "cannot attach photo to this account type"); 239 return; 240 } 241 values.setPhoto(compressed); 242 243 // Finally, invoke the ContactSaveService. 244 Log.v(TAG, "all prerequisites met, about to save photo to contact"); 245 Intent intent = ContactSaveService.createSaveContactIntent( 246 this, 247 deltaList, 248 "", 0, 249 contact.isUserProfile(), 250 null, null, 251 raw.getRawContactId(), 252 mCroppedPhotoUri 253 ); 254 startService(intent); 255 finish(); 256 } 257} 258