Loading src/com/android/contacts/ContactLoader.java +32 −9 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.contacts; import com.android.contacts.model.AccountType; import com.android.contacts.model.AccountTypeManager; import com.android.contacts.model.AccountTypeWithDataSet; import com.android.contacts.model.EntityDeltaList; import com.android.contacts.util.ContactLoaderUtils; import com.android.contacts.util.DataStatus; import com.android.contacts.util.StreamItemEntry; Loading Loading @@ -71,7 +72,7 @@ import java.util.Set; * Loads a single Contact and all it constituent RawContacts. */ public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> { private static final String TAG = "ContactLoader"; private static final String TAG = ContactLoader.class.getSimpleName(); private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); Loading Loading @@ -312,6 +313,13 @@ public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> { return mRequestedUri; } /** * Instantiate a new EntityDeltaList for this contact. */ public EntityDeltaList createEntityDeltaList() { return EntityDeltaList.fromIterator(getEntities().iterator()); } /** * Returns the contact ID. */ Loading Loading @@ -419,16 +427,31 @@ public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> { * writable raw-contact, and false otherwise. */ public boolean isWritableContact(final Context context) { if (isDirectoryEntry()) return false; return getFirstWritableRawContactId(context) != -1; } /** * Return the ID of the first raw-contact in the contact data that belongs to a * contact-writable account, or -1 if no such entity exists. */ public long getFirstWritableRawContactId(final Context context) { // Directory entries are non-writable if (isDirectoryEntry()) return -1; // Iterate through raw-contacts; if we find a writable on, return its ID. final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context); for (Entity rawContact : getEntities()) { final ContentValues rawValues = rawContact.getEntityValues(); final String accountType = rawValues.getAsString(RawContacts.ACCOUNT_TYPE); final String dataSet = rawValues.getAsString(RawContacts.DATA_SET); final AccountType type = accountTypes.getAccountType(accountType, dataSet); if (type != null && type.areContactsWritable()) return true; } return false; for (Entity entity : getEntities()) { ContentValues values = entity.getEntityValues(); String type = values.getAsString(RawContacts.ACCOUNT_TYPE); String dataSet = values.getAsString(RawContacts.DATA_SET); AccountType accountType = accountTypes.getAccountType(type, dataSet); if (accountType != null && accountType.areContactsWritable()) { return values.getAsLong(RawContacts._ID); } } // No writable raw-contact was found. return -1; } public int getDirectoryExportSupport() { Loading src/com/android/contacts/ContactSaveService.java +30 −25 Original line number Diff line number Diff line Loading @@ -222,7 +222,7 @@ public class ContactSaveService extends IntentService { */ public static Intent createNewRawContactIntent(Context context, ArrayList<ContentValues> values, AccountWithDataSet account, Class<?> callbackActivity, String callbackAction) { Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent( context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT); Loading Loading @@ -290,8 +290,9 @@ public class ContactSaveService extends IntentService { * @param updatedPhotoPath denotes a temporary file containing the contact's new photo. */ public static Intent createSaveContactIntent(Context context, EntityDeltaList state, String saveModeExtraKey, int saveMode, boolean isProfile, Class<?> callbackActivity, String callbackAction, long rawContactId, String updatedPhotoPath) { String saveModeExtraKey, int saveMode, boolean isProfile, Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId, String updatedPhotoPath) { Bundle bundle = new Bundle(); bundle.putString(String.valueOf(rawContactId), updatedPhotoPath); return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile, Loading @@ -306,8 +307,9 @@ public class ContactSaveService extends IntentService { * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo. */ public static Intent createSaveContactIntent(Context context, EntityDeltaList state, String saveModeExtraKey, int saveMode, boolean isProfile, Class<?> callbackActivity, String callbackAction, Bundle updatedPhotos) { String saveModeExtraKey, int saveMode, boolean isProfile, Class<? extends Activity> callbackActivity, String callbackAction, Bundle updatedPhotos) { Intent serviceIntent = new Intent( context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT); Loading @@ -317,6 +319,7 @@ public class ContactSaveService extends IntentService { serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos); } if (callbackActivity != null) { // Callback intent will be invoked by the service once the contact is // saved. The service will put the URI of the new contact as "data" on // the callback intent. Loading @@ -324,12 +327,12 @@ public class ContactSaveService extends IntentService { callbackIntent.putExtra(saveModeExtraKey, saveMode); callbackIntent.setAction(callbackAction); serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); } return serviceIntent; } private void saveContact(Intent intent) { EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE); Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false); Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS); Loading Loading @@ -462,6 +465,8 @@ public class ContactSaveService extends IntentService { } } Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); if (callbackIntent != null) { if (succeeded) { // Mark the intent to indicate that the save was successful (even if the lookup URI // is now null). For local contacts or the local profile, it's possible that the Loading @@ -469,9 +474,9 @@ public class ContactSaveService extends IntentService { callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true); } callbackIntent.setData(lookupUri); deliverCallback(callbackIntent); } } /** * Save updated photo for the specified raw-contact. Loading Loading @@ -554,7 +559,7 @@ public class ContactSaveService extends IntentService { * @param callbackAction is the intent action for the callback intent */ public static Intent createNewGroupIntent(Context context, AccountWithDataSet account, String label, long[] rawContactsToAdd, Class<?> callbackActivity, String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP); Loading Loading @@ -618,7 +623,7 @@ public class ContactSaveService extends IntentService { * Creates an intent that can be sent to this service to rename a group. */ public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel, Class<?> callbackActivity, String callbackAction) { Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP); serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); Loading Loading @@ -689,7 +694,7 @@ public class ContactSaveService extends IntentService { */ public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel, long[] rawContactsToAdd, long[] rawContactsToRemove, Class<?> callbackActivity, String callbackAction) { Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP); serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); Loading Loading @@ -959,7 +964,7 @@ public class ContactSaveService extends IntentService { */ public static Intent createJoinContactsIntent(Context context, long contactId1, long contactId2, boolean contactWritable, Class<?> callbackActivity, String callbackAction) { Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS); serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1); Loading src/com/android/contacts/activities/AttachPhotoActivity.java +102 −164 Original line number Diff line number Diff line Loading @@ -18,29 +18,31 @@ package com.android.contacts.activities; import com.android.contacts.ContactsActivity; import com.android.contacts.R; import com.android.contacts.model.ExchangeAccountType; import com.android.contacts.model.GoogleAccountType; import com.android.contacts.model.AccountType; import com.android.contacts.model.EntityDelta; import com.android.contacts.model.EntityDeltaList; import com.android.contacts.model.EntityModifier; import com.android.contacts.util.ContactPhotoUtils; import com.android.contacts.ContactLoader; import com.android.contacts.ContactSaveService; import com.android.contacts.ContactsUtils; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Intent; import android.content.OperationApplicationException; import android.content.Loader; import android.content.Loader.OnLoadCompleteListener; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Photo; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.DisplayPhoto; import android.provider.ContactsContract.RawContacts; import android.widget.Toast; import android.provider.MediaStore; import android.util.Log; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.io.File; /** * Provides an external interface for other applications to attach images Loading @@ -49,25 +51,38 @@ import java.util.ArrayList; * size and give the user a chance to use the face detector. */ public class AttachPhotoActivity extends ContactsActivity { private static final String TAG = AttachPhotoActivity.class.getSimpleName(); private static final int REQUEST_PICK_CONTACT = 1; private static final int REQUEST_CROP_PHOTO = 2; private static final String RAW_CONTACT_URIS_KEY = "raw_contact_uris"; private static final String KEY_CONTACT_URI = "contact_uri"; private static final String KEY_TEMP_PHOTO_URI = "temp_photo_uri"; private Long[] mRawContactIds; private File mTempPhotoFile; private Uri mTempPhotoUri; private ContentResolver mContentResolver; // Height/width (in pixels) to request for the photo - queried from the provider. // Height and width (in pixels) to request for the photo - queried from the provider. private static int mPhotoDim; private Uri mContactUri; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); if (icicle != null) { mRawContactIds = toClassArray(icicle.getLongArray(RAW_CONTACT_URIS_KEY)); final String uri = icicle.getString(KEY_CONTACT_URI); mContactUri = (uri == null) ? null : Uri.parse(uri); mTempPhotoUri = Uri.parse(icicle.getString(KEY_TEMP_PHOTO_URI)); mTempPhotoFile = new File(mTempPhotoUri.getPath()); } else { mTempPhotoFile = ContactPhotoUtils.generateTempPhotoFile(); mTempPhotoUri = Uri.fromFile(mTempPhotoFile); Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType(Contacts.CONTENT_ITEM_TYPE); startActivityForResult(intent, REQUEST_PICK_CONTACT); Loading @@ -89,32 +104,8 @@ public class AttachPhotoActivity extends ContactsActivity { @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mRawContactIds != null && mRawContactIds.length != 0) { outState.putLongArray(RAW_CONTACT_URIS_KEY, toPrimativeArray(mRawContactIds)); } } private static long[] toPrimativeArray(Long[] in) { if (in == null) { return null; } long[] out = new long[in.length]; for (int i = 0; i < in.length; i++) { out[i] = in[i]; } return out; } private static Long[] toClassArray(long[] in) { if (in == null) { return null; } Long[] out = new Long[in.length]; for (int i = 0; i < in.length; i++) { out[i] = in[i]; } return out; if (mContactUri != null) outState.putString(KEY_CONTACT_URI, mContactUri.toString()); outState.putString(KEY_TEMP_PHOTO_URI, mTempPhotoUri.toString()); } @Override Loading @@ -137,147 +128,94 @@ public class AttachPhotoActivity extends ContactsActivity { intent.putExtra("aspectY", 1); intent.putExtra("outputX", mPhotoDim); intent.putExtra("outputY", mPhotoDim); intent.putExtra("return-data", true); intent.putExtra(MediaStore.EXTRA_OUTPUT, mTempPhotoUri); startActivityForResult(intent, REQUEST_CROP_PHOTO); // while they're cropping, convert the contact into a raw_contact final long contactId = ContentUris.parseId(result.getData()); final ArrayList<Long> rawContactIdsList = queryForAllRawContactIds( mContentResolver, contactId); mRawContactIds = new Long[rawContactIdsList.size()]; mRawContactIds = rawContactIdsList.toArray(mRawContactIds); mContactUri = result.getData(); if (mRawContactIds == null || rawContactIdsList.isEmpty()) { Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show(); } } else if (requestCode == REQUEST_CROP_PHOTO) { final Bundle extras = result.getExtras(); if (extras != null && mRawContactIds != null) { Bitmap photo = extras.getParcelable("data"); if (photo != null) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); photo.compress(Bitmap.CompressFormat.PNG, 100, stream); final ContentValues imageValues = new ContentValues(); imageValues.put(Photo.PHOTO, stream.toByteArray()); imageValues.put(RawContacts.Data.IS_SUPER_PRIMARY, 1); // attach the photo to every raw contact for (Long rawContactId : mRawContactIds) { // exchange and google only allow one image, so do an update rather than insert boolean shouldUpdate = false; final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); final Uri rawContactDataUri = Uri.withAppendedPath(rawContactUri, RawContacts.Data.CONTENT_DIRECTORY); insertPhoto(imageValues, rawContactDataUri, true); } } loadContact(mContactUri, new ContactLoader.Listener() { @Override public void onContactLoaded(ContactLoader.Result contact) { saveContact(contact); } finish(); }); } } // TODO: move to background public static ArrayList<Long> queryForAllRawContactIds(ContentResolver cr, long contactId) { Cursor rawContactIdCursor = null; ArrayList<Long> rawContactIds = new ArrayList<Long>(); // TODO: consider moving this to ContactLoader, especially if we keep adding similar // code elsewhere (ViewNotificationService is another case). The only concern is that, // although this is convenient, it isn't quite as robust as using LoaderManager... for // instance, the loader doesn't persist across Activity restarts. private void loadContact(Uri contactUri, final ContactLoader.Listener listener) { final ContactLoader loader = new ContactLoader(this, contactUri); loader.registerListener(0, new OnLoadCompleteListener<ContactLoader.Result>() { @Override public void onLoadComplete( Loader<ContactLoader.Result> loader, ContactLoader.Result contact) { try { rawContactIdCursor = cr.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID}, RawContacts.CONTACT_ID + "=" + contactId, null, null); if (rawContactIdCursor != null) { while (rawContactIdCursor.moveToNext()) { rawContactIds.add(rawContactIdCursor.getLong(0)); } loader.reset(); } } finally { if (rawContactIdCursor != null) { rawContactIdCursor.close(); catch (RuntimeException e) { Log.e(TAG, "Error resetting loader", e); } listener.onContactLoaded(contact); } return rawContactIds; }); loader.startLoading(); } /** * Inserts a photo on the raw contact. * @param values the photo values * @param assertAccount if true, will check to verify that no photos exist for Google, * Exchange and unsynced phone account types. These account types only take one picture, * so if one exists, the account will be updated with the new photo. * If prerequisites have been met, attach the photo to a raw-contact and save. * The prerequisites are: * - photo has been cropped * - contact has been loaded */ private void insertPhoto(ContentValues values, Uri rawContactDataUri, boolean assertAccount) { ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); private void saveContact(ContactLoader.Result contact) { if (assertAccount) { // Make sure no pictures exist for Google, Exchange and unsynced phone accounts. operations.add(ContentProviderOperation.newAssertQuery(rawContactDataUri) .withSelection(Photo.MIMETYPE + "=? AND " + RawContacts.DATA_SET + " IS NULL AND (" + RawContacts.ACCOUNT_TYPE + " IN (?,?) OR " + RawContacts.ACCOUNT_TYPE + " IS NULL)", new String[] {Photo.CONTENT_ITEM_TYPE, GoogleAccountType.ACCOUNT_TYPE, ExchangeAccountType.ACCOUNT_TYPE}) .withExpectedCount(0).build()); // Obtain the raw-contact that we will save to. EntityDeltaList deltaList = contact.createEntityDeltaList(); EntityDelta raw = deltaList.getFirstWritableRawContact(this); if (raw == null) { Log.w(TAG, "no writable raw-contact found"); return; } // insert the photo values.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE); operations.add(ContentProviderOperation.newInsert(rawContactDataUri) .withValues(values).build()); try { mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations); } catch (RemoteException e) { throw new IllegalStateException("Problem querying raw_contacts/data", e); } catch (OperationApplicationException e) { // the account doesn't allow multiple photos, so update if (assertAccount) { updatePhoto(values, rawContactDataUri, false); } else { throw new IllegalStateException("Problem inserting photo into raw_contacts/data", e); } } // Create a scaled, compressed bitmap to add to the entity-delta list. final int size = ContactsUtils.getThumbnailSize(this); final Bitmap bitmap = BitmapFactory.decodeFile(mTempPhotoFile.getAbsolutePath()); final Bitmap scaled = Bitmap.createScaledBitmap(bitmap, size, size, false); final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled); if (compressed == null) { Log.w(TAG, "could not create scaled and compressed Bitmap"); return; } /** * Tries to update the photo on the raw_contact. If no photo exists, and allowInsert == true, * then will try to {@link #updatePhoto(ContentValues, boolean)} */ private void updatePhoto(ContentValues values, Uri rawContactDataUri, boolean allowInsert) { ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); values.remove(Photo.MIMETYPE); // check that a photo exists operations.add(ContentProviderOperation.newAssertQuery(rawContactDataUri) .withSelection(Photo.MIMETYPE + "=?", new String[] { Photo.CONTENT_ITEM_TYPE }).withExpectedCount(1).build()); // update that photo operations.add(ContentProviderOperation.newUpdate(rawContactDataUri) .withSelection(Photo.MIMETYPE + "=?", new String[] {Photo.CONTENT_ITEM_TYPE}) .withValues(values).build()); try { mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations); } catch (RemoteException e) { throw new IllegalStateException("Problem querying raw_contacts/data", e); } catch (OperationApplicationException e) { if (allowInsert) { // they deleted the photo between insert and update, so insert one insertPhoto(values, rawContactDataUri, false); } else { throw new IllegalStateException("Problem inserting photo raw_contacts/data", e); } // Add compressed bitmap to entity-delta... this allows us to save to // a new contact; otherwise the entity-delta-list would be empty, and // the ContactSaveService would not create the new contact, and the // full-res photo would fail to be saved to the non-existent contact. AccountType account = raw.getRawContactAccountType(this); EntityDelta.ValuesDelta values = EntityModifier.ensureKindExists(raw, account, Photo.CONTENT_ITEM_TYPE); if (values == null) { Log.w(TAG, "cannot attach photo to this account type"); return; } values.put(Photo.PHOTO, compressed); // Finally, invoke the ContactSaveService. Log.v(TAG, "all prerequisites met, about to save photo to contact"); Intent intent = ContactSaveService.createSaveContactIntent( this, deltaList, "", 0, contact.isUserProfile(), null, null, raw.getRawContactId(), mTempPhotoFile.getAbsolutePath()); startService(intent); finish(); } } src/com/android/contacts/activities/ConfirmAddDetailActivity.java +4 −13 Original line number Diff line number Diff line Loading @@ -560,19 +560,10 @@ public class ConfirmAddDetailActivity extends Activity implements } public void findEditableRawContact() { if (mEntityDeltaList == null) { return; } for (EntityDelta state : mEntityDeltaList) { final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE); final String dataSet = state.getValues().getAsString(RawContacts.DATA_SET); final AccountType type = mAccountTypeManager.getAccountType(accountType, dataSet); if (type.areContactsWritable()) { mEditableAccountType = type; mState = state; return; } if (mEntityDeltaList == null) return; mState = mEntityDeltaList.getFirstWritableRawContact(this); if (mState != null) { mEditableAccountType = mState.getRawContactAccountType(this); } } Loading src/com/android/contacts/activities/PhotoSelectionActivity.java +20 −29 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/com/android/contacts/ContactLoader.java +32 −9 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.contacts; import com.android.contacts.model.AccountType; import com.android.contacts.model.AccountTypeManager; import com.android.contacts.model.AccountTypeWithDataSet; import com.android.contacts.model.EntityDeltaList; import com.android.contacts.util.ContactLoaderUtils; import com.android.contacts.util.DataStatus; import com.android.contacts.util.StreamItemEntry; Loading Loading @@ -71,7 +72,7 @@ import java.util.Set; * Loads a single Contact and all it constituent RawContacts. */ public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> { private static final String TAG = "ContactLoader"; private static final String TAG = ContactLoader.class.getSimpleName(); private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); Loading Loading @@ -312,6 +313,13 @@ public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> { return mRequestedUri; } /** * Instantiate a new EntityDeltaList for this contact. */ public EntityDeltaList createEntityDeltaList() { return EntityDeltaList.fromIterator(getEntities().iterator()); } /** * Returns the contact ID. */ Loading Loading @@ -419,16 +427,31 @@ public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> { * writable raw-contact, and false otherwise. */ public boolean isWritableContact(final Context context) { if (isDirectoryEntry()) return false; return getFirstWritableRawContactId(context) != -1; } /** * Return the ID of the first raw-contact in the contact data that belongs to a * contact-writable account, or -1 if no such entity exists. */ public long getFirstWritableRawContactId(final Context context) { // Directory entries are non-writable if (isDirectoryEntry()) return -1; // Iterate through raw-contacts; if we find a writable on, return its ID. final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context); for (Entity rawContact : getEntities()) { final ContentValues rawValues = rawContact.getEntityValues(); final String accountType = rawValues.getAsString(RawContacts.ACCOUNT_TYPE); final String dataSet = rawValues.getAsString(RawContacts.DATA_SET); final AccountType type = accountTypes.getAccountType(accountType, dataSet); if (type != null && type.areContactsWritable()) return true; } return false; for (Entity entity : getEntities()) { ContentValues values = entity.getEntityValues(); String type = values.getAsString(RawContacts.ACCOUNT_TYPE); String dataSet = values.getAsString(RawContacts.DATA_SET); AccountType accountType = accountTypes.getAccountType(type, dataSet); if (accountType != null && accountType.areContactsWritable()) { return values.getAsLong(RawContacts._ID); } } // No writable raw-contact was found. return -1; } public int getDirectoryExportSupport() { Loading
src/com/android/contacts/ContactSaveService.java +30 −25 Original line number Diff line number Diff line Loading @@ -222,7 +222,7 @@ public class ContactSaveService extends IntentService { */ public static Intent createNewRawContactIntent(Context context, ArrayList<ContentValues> values, AccountWithDataSet account, Class<?> callbackActivity, String callbackAction) { Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent( context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT); Loading Loading @@ -290,8 +290,9 @@ public class ContactSaveService extends IntentService { * @param updatedPhotoPath denotes a temporary file containing the contact's new photo. */ public static Intent createSaveContactIntent(Context context, EntityDeltaList state, String saveModeExtraKey, int saveMode, boolean isProfile, Class<?> callbackActivity, String callbackAction, long rawContactId, String updatedPhotoPath) { String saveModeExtraKey, int saveMode, boolean isProfile, Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId, String updatedPhotoPath) { Bundle bundle = new Bundle(); bundle.putString(String.valueOf(rawContactId), updatedPhotoPath); return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile, Loading @@ -306,8 +307,9 @@ public class ContactSaveService extends IntentService { * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo. */ public static Intent createSaveContactIntent(Context context, EntityDeltaList state, String saveModeExtraKey, int saveMode, boolean isProfile, Class<?> callbackActivity, String callbackAction, Bundle updatedPhotos) { String saveModeExtraKey, int saveMode, boolean isProfile, Class<? extends Activity> callbackActivity, String callbackAction, Bundle updatedPhotos) { Intent serviceIntent = new Intent( context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT); Loading @@ -317,6 +319,7 @@ public class ContactSaveService extends IntentService { serviceIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos); } if (callbackActivity != null) { // Callback intent will be invoked by the service once the contact is // saved. The service will put the URI of the new contact as "data" on // the callback intent. Loading @@ -324,12 +327,12 @@ public class ContactSaveService extends IntentService { callbackIntent.putExtra(saveModeExtraKey, saveMode); callbackIntent.setAction(callbackAction); serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent); } return serviceIntent; } private void saveContact(Intent intent) { EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE); Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false); Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS); Loading Loading @@ -462,6 +465,8 @@ public class ContactSaveService extends IntentService { } } Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); if (callbackIntent != null) { if (succeeded) { // Mark the intent to indicate that the save was successful (even if the lookup URI // is now null). For local contacts or the local profile, it's possible that the Loading @@ -469,9 +474,9 @@ public class ContactSaveService extends IntentService { callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true); } callbackIntent.setData(lookupUri); deliverCallback(callbackIntent); } } /** * Save updated photo for the specified raw-contact. Loading Loading @@ -554,7 +559,7 @@ public class ContactSaveService extends IntentService { * @param callbackAction is the intent action for the callback intent */ public static Intent createNewGroupIntent(Context context, AccountWithDataSet account, String label, long[] rawContactsToAdd, Class<?> callbackActivity, String label, long[] rawContactsToAdd, Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP); Loading Loading @@ -618,7 +623,7 @@ public class ContactSaveService extends IntentService { * Creates an intent that can be sent to this service to rename a group. */ public static Intent createGroupRenameIntent(Context context, long groupId, String newLabel, Class<?> callbackActivity, String callbackAction) { Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_RENAME_GROUP); serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); Loading Loading @@ -689,7 +694,7 @@ public class ContactSaveService extends IntentService { */ public static Intent createGroupUpdateIntent(Context context, long groupId, String newLabel, long[] rawContactsToAdd, long[] rawContactsToRemove, Class<?> callbackActivity, String callbackAction) { Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_UPDATE_GROUP); serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_ID, groupId); Loading Loading @@ -959,7 +964,7 @@ public class ContactSaveService extends IntentService { */ public static Intent createJoinContactsIntent(Context context, long contactId1, long contactId2, boolean contactWritable, Class<?> callbackActivity, String callbackAction) { Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS); serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1); Loading
src/com/android/contacts/activities/AttachPhotoActivity.java +102 −164 Original line number Diff line number Diff line Loading @@ -18,29 +18,31 @@ package com.android.contacts.activities; import com.android.contacts.ContactsActivity; import com.android.contacts.R; import com.android.contacts.model.ExchangeAccountType; import com.android.contacts.model.GoogleAccountType; import com.android.contacts.model.AccountType; import com.android.contacts.model.EntityDelta; import com.android.contacts.model.EntityDeltaList; import com.android.contacts.model.EntityModifier; import com.android.contacts.util.ContactPhotoUtils; import com.android.contacts.ContactLoader; import com.android.contacts.ContactSaveService; import com.android.contacts.ContactsUtils; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Intent; import android.content.OperationApplicationException; import android.content.Loader; import android.content.Loader.OnLoadCompleteListener; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Photo; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.DisplayPhoto; import android.provider.ContactsContract.RawContacts; import android.widget.Toast; import android.provider.MediaStore; import android.util.Log; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.io.File; /** * Provides an external interface for other applications to attach images Loading @@ -49,25 +51,38 @@ import java.util.ArrayList; * size and give the user a chance to use the face detector. */ public class AttachPhotoActivity extends ContactsActivity { private static final String TAG = AttachPhotoActivity.class.getSimpleName(); private static final int REQUEST_PICK_CONTACT = 1; private static final int REQUEST_CROP_PHOTO = 2; private static final String RAW_CONTACT_URIS_KEY = "raw_contact_uris"; private static final String KEY_CONTACT_URI = "contact_uri"; private static final String KEY_TEMP_PHOTO_URI = "temp_photo_uri"; private Long[] mRawContactIds; private File mTempPhotoFile; private Uri mTempPhotoUri; private ContentResolver mContentResolver; // Height/width (in pixels) to request for the photo - queried from the provider. // Height and width (in pixels) to request for the photo - queried from the provider. private static int mPhotoDim; private Uri mContactUri; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); if (icicle != null) { mRawContactIds = toClassArray(icicle.getLongArray(RAW_CONTACT_URIS_KEY)); final String uri = icicle.getString(KEY_CONTACT_URI); mContactUri = (uri == null) ? null : Uri.parse(uri); mTempPhotoUri = Uri.parse(icicle.getString(KEY_TEMP_PHOTO_URI)); mTempPhotoFile = new File(mTempPhotoUri.getPath()); } else { mTempPhotoFile = ContactPhotoUtils.generateTempPhotoFile(); mTempPhotoUri = Uri.fromFile(mTempPhotoFile); Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType(Contacts.CONTENT_ITEM_TYPE); startActivityForResult(intent, REQUEST_PICK_CONTACT); Loading @@ -89,32 +104,8 @@ public class AttachPhotoActivity extends ContactsActivity { @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mRawContactIds != null && mRawContactIds.length != 0) { outState.putLongArray(RAW_CONTACT_URIS_KEY, toPrimativeArray(mRawContactIds)); } } private static long[] toPrimativeArray(Long[] in) { if (in == null) { return null; } long[] out = new long[in.length]; for (int i = 0; i < in.length; i++) { out[i] = in[i]; } return out; } private static Long[] toClassArray(long[] in) { if (in == null) { return null; } Long[] out = new Long[in.length]; for (int i = 0; i < in.length; i++) { out[i] = in[i]; } return out; if (mContactUri != null) outState.putString(KEY_CONTACT_URI, mContactUri.toString()); outState.putString(KEY_TEMP_PHOTO_URI, mTempPhotoUri.toString()); } @Override Loading @@ -137,147 +128,94 @@ public class AttachPhotoActivity extends ContactsActivity { intent.putExtra("aspectY", 1); intent.putExtra("outputX", mPhotoDim); intent.putExtra("outputY", mPhotoDim); intent.putExtra("return-data", true); intent.putExtra(MediaStore.EXTRA_OUTPUT, mTempPhotoUri); startActivityForResult(intent, REQUEST_CROP_PHOTO); // while they're cropping, convert the contact into a raw_contact final long contactId = ContentUris.parseId(result.getData()); final ArrayList<Long> rawContactIdsList = queryForAllRawContactIds( mContentResolver, contactId); mRawContactIds = new Long[rawContactIdsList.size()]; mRawContactIds = rawContactIdsList.toArray(mRawContactIds); mContactUri = result.getData(); if (mRawContactIds == null || rawContactIdsList.isEmpty()) { Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show(); } } else if (requestCode == REQUEST_CROP_PHOTO) { final Bundle extras = result.getExtras(); if (extras != null && mRawContactIds != null) { Bitmap photo = extras.getParcelable("data"); if (photo != null) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); photo.compress(Bitmap.CompressFormat.PNG, 100, stream); final ContentValues imageValues = new ContentValues(); imageValues.put(Photo.PHOTO, stream.toByteArray()); imageValues.put(RawContacts.Data.IS_SUPER_PRIMARY, 1); // attach the photo to every raw contact for (Long rawContactId : mRawContactIds) { // exchange and google only allow one image, so do an update rather than insert boolean shouldUpdate = false; final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); final Uri rawContactDataUri = Uri.withAppendedPath(rawContactUri, RawContacts.Data.CONTENT_DIRECTORY); insertPhoto(imageValues, rawContactDataUri, true); } } loadContact(mContactUri, new ContactLoader.Listener() { @Override public void onContactLoaded(ContactLoader.Result contact) { saveContact(contact); } finish(); }); } } // TODO: move to background public static ArrayList<Long> queryForAllRawContactIds(ContentResolver cr, long contactId) { Cursor rawContactIdCursor = null; ArrayList<Long> rawContactIds = new ArrayList<Long>(); // TODO: consider moving this to ContactLoader, especially if we keep adding similar // code elsewhere (ViewNotificationService is another case). The only concern is that, // although this is convenient, it isn't quite as robust as using LoaderManager... for // instance, the loader doesn't persist across Activity restarts. private void loadContact(Uri contactUri, final ContactLoader.Listener listener) { final ContactLoader loader = new ContactLoader(this, contactUri); loader.registerListener(0, new OnLoadCompleteListener<ContactLoader.Result>() { @Override public void onLoadComplete( Loader<ContactLoader.Result> loader, ContactLoader.Result contact) { try { rawContactIdCursor = cr.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID}, RawContacts.CONTACT_ID + "=" + contactId, null, null); if (rawContactIdCursor != null) { while (rawContactIdCursor.moveToNext()) { rawContactIds.add(rawContactIdCursor.getLong(0)); } loader.reset(); } } finally { if (rawContactIdCursor != null) { rawContactIdCursor.close(); catch (RuntimeException e) { Log.e(TAG, "Error resetting loader", e); } listener.onContactLoaded(contact); } return rawContactIds; }); loader.startLoading(); } /** * Inserts a photo on the raw contact. * @param values the photo values * @param assertAccount if true, will check to verify that no photos exist for Google, * Exchange and unsynced phone account types. These account types only take one picture, * so if one exists, the account will be updated with the new photo. * If prerequisites have been met, attach the photo to a raw-contact and save. * The prerequisites are: * - photo has been cropped * - contact has been loaded */ private void insertPhoto(ContentValues values, Uri rawContactDataUri, boolean assertAccount) { ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); private void saveContact(ContactLoader.Result contact) { if (assertAccount) { // Make sure no pictures exist for Google, Exchange and unsynced phone accounts. operations.add(ContentProviderOperation.newAssertQuery(rawContactDataUri) .withSelection(Photo.MIMETYPE + "=? AND " + RawContacts.DATA_SET + " IS NULL AND (" + RawContacts.ACCOUNT_TYPE + " IN (?,?) OR " + RawContacts.ACCOUNT_TYPE + " IS NULL)", new String[] {Photo.CONTENT_ITEM_TYPE, GoogleAccountType.ACCOUNT_TYPE, ExchangeAccountType.ACCOUNT_TYPE}) .withExpectedCount(0).build()); // Obtain the raw-contact that we will save to. EntityDeltaList deltaList = contact.createEntityDeltaList(); EntityDelta raw = deltaList.getFirstWritableRawContact(this); if (raw == null) { Log.w(TAG, "no writable raw-contact found"); return; } // insert the photo values.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE); operations.add(ContentProviderOperation.newInsert(rawContactDataUri) .withValues(values).build()); try { mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations); } catch (RemoteException e) { throw new IllegalStateException("Problem querying raw_contacts/data", e); } catch (OperationApplicationException e) { // the account doesn't allow multiple photos, so update if (assertAccount) { updatePhoto(values, rawContactDataUri, false); } else { throw new IllegalStateException("Problem inserting photo into raw_contacts/data", e); } } // Create a scaled, compressed bitmap to add to the entity-delta list. final int size = ContactsUtils.getThumbnailSize(this); final Bitmap bitmap = BitmapFactory.decodeFile(mTempPhotoFile.getAbsolutePath()); final Bitmap scaled = Bitmap.createScaledBitmap(bitmap, size, size, false); final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled); if (compressed == null) { Log.w(TAG, "could not create scaled and compressed Bitmap"); return; } /** * Tries to update the photo on the raw_contact. If no photo exists, and allowInsert == true, * then will try to {@link #updatePhoto(ContentValues, boolean)} */ private void updatePhoto(ContentValues values, Uri rawContactDataUri, boolean allowInsert) { ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); values.remove(Photo.MIMETYPE); // check that a photo exists operations.add(ContentProviderOperation.newAssertQuery(rawContactDataUri) .withSelection(Photo.MIMETYPE + "=?", new String[] { Photo.CONTENT_ITEM_TYPE }).withExpectedCount(1).build()); // update that photo operations.add(ContentProviderOperation.newUpdate(rawContactDataUri) .withSelection(Photo.MIMETYPE + "=?", new String[] {Photo.CONTENT_ITEM_TYPE}) .withValues(values).build()); try { mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations); } catch (RemoteException e) { throw new IllegalStateException("Problem querying raw_contacts/data", e); } catch (OperationApplicationException e) { if (allowInsert) { // they deleted the photo between insert and update, so insert one insertPhoto(values, rawContactDataUri, false); } else { throw new IllegalStateException("Problem inserting photo raw_contacts/data", e); } // Add compressed bitmap to entity-delta... this allows us to save to // a new contact; otherwise the entity-delta-list would be empty, and // the ContactSaveService would not create the new contact, and the // full-res photo would fail to be saved to the non-existent contact. AccountType account = raw.getRawContactAccountType(this); EntityDelta.ValuesDelta values = EntityModifier.ensureKindExists(raw, account, Photo.CONTENT_ITEM_TYPE); if (values == null) { Log.w(TAG, "cannot attach photo to this account type"); return; } values.put(Photo.PHOTO, compressed); // Finally, invoke the ContactSaveService. Log.v(TAG, "all prerequisites met, about to save photo to contact"); Intent intent = ContactSaveService.createSaveContactIntent( this, deltaList, "", 0, contact.isUserProfile(), null, null, raw.getRawContactId(), mTempPhotoFile.getAbsolutePath()); startService(intent); finish(); } }
src/com/android/contacts/activities/ConfirmAddDetailActivity.java +4 −13 Original line number Diff line number Diff line Loading @@ -560,19 +560,10 @@ public class ConfirmAddDetailActivity extends Activity implements } public void findEditableRawContact() { if (mEntityDeltaList == null) { return; } for (EntityDelta state : mEntityDeltaList) { final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE); final String dataSet = state.getValues().getAsString(RawContacts.DATA_SET); final AccountType type = mAccountTypeManager.getAccountType(accountType, dataSet); if (type.areContactsWritable()) { mEditableAccountType = type; mState = state; return; } if (mEntityDeltaList == null) return; mState = mEntityDeltaList.getFirstWritableRawContact(this); if (mState != null) { mEditableAccountType = mState.getRawContactAccountType(this); } } Loading
src/com/android/contacts/activities/PhotoSelectionActivity.java +20 −29 File changed.Preview size limit exceeded, changes collapsed. Show changes