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