Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cda48e8c authored by Josh Gargus's avatar Josh Gargus Committed by Android (Google) Code Review
Browse files

Merge "Fix regression; once again we can apply Gallery photo to contact."

parents b1503df1 e5d3f897
Loading
Loading
Loading
Loading
+32 −9
Original line number Diff line number Diff line
@@ -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;
@@ -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);

@@ -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.
         */
@@ -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() {
+30 −25
Original line number Diff line number Diff line
@@ -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);
@@ -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,
@@ -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);
@@ -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.
@@ -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);

@@ -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
@@ -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.
@@ -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);
@@ -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);
@@ -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);
@@ -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);
+102 −164
Original line number Diff line number Diff line
@@ -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
@@ -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);
@@ -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
@@ -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();
    }
}
+4 −13
Original line number Diff line number Diff line
@@ -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);
        }
    }

+20 −29

File changed.

Preview size limit exceeded, changes collapsed.

Loading