diff options
author | Dave Santoro <dsantoro@google.com> | 2011-07-15 15:27:00 -0700 |
---|---|---|
committer | Dave Santoro <dsantoro@google.com> | 2011-07-20 11:33:36 -0700 |
commit | 6802030a777c0c3ba1dc029c534cca4784260632 (patch) | |
tree | bd0e1ad996046809541c63ceaee9f73c528aa890 | |
parent | 1fafab3782590ab080dab07651df5aed0768e154 (diff) | |
download | packages_providers_ContactsProvider-6802030a777c0c3ba1dc029c534cca4784260632.zip packages_providers_ContactsProvider-6802030a777c0c3ba1dc029c534cca4784260632.tar.gz packages_providers_ContactsProvider-6802030a777c0c3ba1dc029c534cca4784260632.tar.bz2 |
Forward-compatibility of old status update API.
With this change, inserts or updates to the old status update API
will be mirrored as stream item inserts or updates in the new
social stream API. This is primarily to bootstrap the new stream
data (which is what the UI will be showing) until such time as data
providers start using the new API.
This change also includes migration to using the new photo storage
system for photos from the social stream API.
Change-Id: I0974444077790f706637dd6b9d1f6f50d204aa6c
-rw-r--r-- | res/values/config.xml | 8 | ||||
-rw-r--r-- | src/com/android/providers/contacts/ContactsDatabaseHelper.java | 71 | ||||
-rw-r--r-- | src/com/android/providers/contacts/ContactsProvider2.java | 245 | ||||
-rw-r--r-- | src/com/android/providers/contacts/PhotoStore.java | 20 | ||||
-rw-r--r-- | tests/src/com/android/providers/contacts/ContactsProvider2Test.java | 116 |
5 files changed, 345 insertions, 115 deletions
diff --git a/res/values/config.xml b/res/values/config.xml index fc63a50..426fcc2 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -16,10 +16,10 @@ <resources> - <!-- Maximum size of photos inserted in social stream items --> - <integer name="config_stream_item_photo_max_bytes">71680</integer> - - <!-- Maximum dimension (height or width) of contact display photos --> + <!-- + Maximum dimension (height or width) of contact display photos or + photos from the social stream. + --> <integer name="config_max_display_photo_dim">256</integer> <!-- Maximum dimension (height or width) of contact photo thumbnails --> diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java index 10bc39c..44a0bc5 100644 --- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java +++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java @@ -102,7 +102,7 @@ import java.util.Locale; * 600-699 Ice Cream Sandwich * </pre> */ - static final int DATABASE_VERSION = 608; + static final int DATABASE_VERSION = 609; private static final String DATABASE_NAME = "contacts2.db"; private static final String DATABASE_PRESENCE = "presence_db"; @@ -508,7 +508,8 @@ import java.util.Locale; String CONCRETE_STREAM_ITEM_ID = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.STREAM_ITEM_ID; String CONCRETE_SORT_INDEX = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.SORT_INDEX; - String CONCRETE_PICTURE = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.PICTURE; + String CONCRETE_PHOTO_FILE_ID = Tables.STREAM_ITEM_PHOTOS + "." + + StreamItemPhotos.PHOTO_FILE_ID; String CONCRETE_ACTION = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.ACTION; String CONCRETE_ACTION_URI = Tables.STREAM_ITEM_PHOTOS + "." + StreamItemPhotos.ACTION_URI; } @@ -881,7 +882,7 @@ import java.util.Locale; db.execSQL("CREATE TABLE " + Tables.STREAM_ITEMS + " (" + StreamItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + StreamItems.RAW_CONTACT_ID + " INTEGER NOT NULL, " + - StreamItems.RES_PACKAGE + " INTEGER NOT NULL, " + + StreamItems.RES_PACKAGE + " TEXT, " + StreamItems.RES_ICON + " INTEGER, " + StreamItems.RES_LABEL + " INTEGER, " + StreamItems.TEXT + " TEXT NOT NULL, " + @@ -896,7 +897,7 @@ import java.util.Locale; StreamItemPhotos._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + StreamItemPhotos.STREAM_ITEM_ID + " INTEGER NOT NULL, " + StreamItemPhotos.SORT_INDEX + " INTEGER, " + - StreamItemPhotos.PICTURE + " BLOB, " + + StreamItemPhotos.PHOTO_FILE_ID + " INTEGER NOT NULL, " + StreamItemPhotos.ACTION + " TEXT, " + StreamItemPhotos.ACTION_URI + " TEXT, " + "FOREIGN KEY(" + StreamItemPhotos.STREAM_ITEM_ID + ") REFERENCES " + @@ -2042,6 +2043,11 @@ import java.util.Locale; oldVersion = 608; } + if (oldVersion < 609) { + upgradeToVersion609(db); + oldVersion = 609; + } + if (upgradeViewsAndTriggers) { createContactsViews(db); @@ -3130,27 +3136,9 @@ import java.util.Locale; } private void upgradeToVersion605(SQLiteDatabase db) { - db.execSQL("CREATE TABLE stream_items(" + - "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "raw_contact_id INTEGER NOT NULL, " + - "package_id INTEGER NOT NULL, " + - "icon INTEGER, " + - "label INTEGER, " + - "text TEXT NOT NULL, " + - "timestamp INTEGER NOT NULL, " + - "comments TEXT NOT NULL, " + - "action TEXT, " + - "action_uri TEXT, " + - "FOREIGN KEY(raw_contact_id) REFERENCES raw_contacts(_id));"); - - db.execSQL("CREATE TABLE stream_item_photos(" + - "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "stream_item_id INTEGER NOT NULL, " + - "sort_index INTEGER, " + - "picture BLOB, " + - "action TEXT, " + - "action_uri TEXT, " + - "FOREIGN KEY(stream_item_id) REFERENCES stream_items(_id));"); + // This version used to create the stream item and stream item photos tables, but a newer + // version of those tables is created in version 609 below. So omitting the creation in + // this upgrade step to avoid a create->drop->create. } private void upgradeToVersion606(SQLiteDatabase db) { @@ -3181,6 +3169,39 @@ import java.util.Locale; "filesize INTEGER NOT NULL);"); } + private void upgradeToVersion609(SQLiteDatabase db) { + // The stream item and stream item photos APIs were not in-use by anyone in the time + // between their initial creation (in v605) and this update. So we're just dropping + // and re-creating them to get appropriate columns. The delta is as follows: + // - In stream_items, package_id was replaced by res_package. + // - In stream_item_photos, picture was replaced by photo_file_id. + + db.execSQL("DROP TABLE IF EXISTS stream_items"); + db.execSQL("DROP TABLE IF EXISTS stream_item_photos"); + + db.execSQL("CREATE TABLE stream_items(" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "raw_contact_id INTEGER NOT NULL, " + + "res_package TEXT, " + + "icon INTEGER, " + + "label INTEGER, " + + "text TEXT NOT NULL, " + + "timestamp INTEGER NOT NULL, " + + "comments TEXT NOT NULL, " + + "action TEXT, " + + "action_uri TEXT, " + + "FOREIGN KEY(raw_contact_id) REFERENCES raw_contacts(_id));"); + + db.execSQL("CREATE TABLE stream_item_photos(" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "stream_item_id INTEGER NOT NULL, " + + "sort_index INTEGER, " + + "photo_file_id INTEGER NOT NULL, " + + "action TEXT, " + + "action_uri TEXT, " + + "FOREIGN KEY(stream_item_id) REFERENCES stream_items(_id));"); + } + public String extractHandleFromEmailAddress(String email) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email); if (tokens.length == 0) { diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 6bbbfef..67a3540 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -404,13 +404,17 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun public static final String[] PROJECTION = new String[] { RawContactsColumns.CONCRETE_ID, + RawContactsColumns.CONCRETE_ACCOUNT_TYPE, + RawContactsColumns.CONCRETE_ACCOUNT_NAME, DataColumns.CONCRETE_ID, ContactsColumns.CONCRETE_ID }; public static final int RAW_CONTACT_ID = 0; - public static final int DATA_ID = 1; - public static final int CONTACT_ID = 2; + public static final int ACCOUNT_TYPE = 1; + public static final int ACCOUNT_NAME = 2; + public static final int DATA_ID = 3; + public static final int CONTACT_ID = 4; } interface RawContactsQuery { @@ -896,7 +900,9 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun .add(StreamItems.RAW_CONTACT_ID) .add(StreamItemPhotos.STREAM_ITEM_ID) .add(StreamItemPhotos.SORT_INDEX) - .add(StreamItemPhotos.PICTURE) + .add(StreamItemPhotos.PHOTO_FILE_ID) + .add(StreamItemPhotos.PHOTO_URI, + "'" + DisplayPhoto.CONTENT_URI + "'||'/'||" + StreamItemPhotos.PHOTO_FILE_ID) .add(StreamItemPhotos.ACTION, StreamItemPhotosColumns.CONCRETE_ACTION) .add(StreamItemPhotos.ACTION_URI, StreamItemPhotosColumns.CONCRETE_ACTION_URI) .build(); @@ -1180,9 +1186,6 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun private ProfileIdCache mProfileIdCache; - /** Limit for the maximum byte size of social stream item photos (loaded from config.xml). */ - private int mMaxStreamItemPhotoSizeBytes; - /** * Maximum dimension (height or width) of display photos. Larger images will be scaled * to fit. @@ -1252,8 +1255,6 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); Resources resources = getContext().getResources(); - mMaxStreamItemPhotoSizeBytes = resources.getInteger( - R.integer.config_stream_item_photo_max_bytes); mMaxDisplayPhotoDim = resources.getInteger( R.integer.config_max_display_photo_dim); mMaxThumbnailPhotoDim = resources.getInteger( @@ -1517,44 +1518,77 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun /* Visible for testing */ protected void cleanupPhotoStore() { - // Assemble the set of photo store keys that are in use, and send those to the photo + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + + // Assemble the set of photo store file IDs that are in use, and send those to the photo // store. Any photos that aren't in that set will be deleted, and any photos that no // longer exist in the photo store will be returned for us to clear out in the DB. - Cursor c = mDb.query(Views.DATA, new String[]{Data._ID, Photo.PHOTO_FILE_ID}, + Cursor c = db.query(Views.DATA, new String[]{Data._ID, Photo.PHOTO_FILE_ID}, Data.MIMETYPE + "=" + Photo.MIMETYPE + " AND " + Photo.PHOTO_FILE_ID + " IS NOT NULL", null, null, null, null); - Set<Long> usedKeys = Sets.newHashSet(); - Map<Long, List<Long>> keysToIdList = Maps.newHashMap(); + Set<Long> usedPhotoFileIds = Sets.newHashSet(); + Map<Long, Long> photoFileIdToDataId = Maps.newHashMap(); try { while (c.moveToNext()) { - long id = c.getLong(0); - long key = c.getLong(1); - usedKeys.add(key); - List<Long> ids = keysToIdList.get(key); - if (ids == null) { - ids = Lists.newArrayList(); - } - ids.add(id); - keysToIdList.put(key, ids); + long dataId = c.getLong(0); + long photoFileId = c.getLong(1); + usedPhotoFileIds.add(photoFileId); + photoFileIdToDataId.put(photoFileId, dataId); + } + } finally { + c.close(); + } + + // Also query for all social stream item photos. + c = db.query(Tables.STREAM_ITEM_PHOTOS, + new String[]{ + StreamItemPhotos._ID, + StreamItemPhotos.STREAM_ITEM_ID, + StreamItemPhotos.PHOTO_FILE_ID + }, + null, null, null, null, null); + Map<Long, Long> photoFileIdToStreamItemPhotoId = Maps.newHashMap(); + Map<Long, Long> streamItemPhotoIdToStreamItemId = Maps.newHashMap(); + try { + while (c.moveToNext()) { + long streamItemPhotoId = c.getLong(0); + long streamItemId = c.getLong(1); + long photoFileId = c.getLong(2); + usedPhotoFileIds.add(photoFileId); + photoFileIdToStreamItemPhotoId.put(photoFileId, streamItemPhotoId); + streamItemPhotoIdToStreamItemId.put(streamItemPhotoId, streamItemId); } } finally { c.close(); } // Run the photo store cleanup. - Set<Long> missingKeys = mPhotoStore.cleanup(usedKeys); + Set<Long> missingPhotoIds = mPhotoStore.cleanup(usedPhotoFileIds); // If any of the keys we're using no longer exist, clean them up. - if (!missingKeys.isEmpty()) { + if (!missingPhotoIds.isEmpty()) { ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); - for (long key : missingKeys) { - for (long id : keysToIdList.get(key)) { + for (long missingPhotoId : missingPhotoIds) { + if (photoFileIdToDataId.containsKey(missingPhotoId)) { + long dataId = photoFileIdToDataId.get(missingPhotoId); ContentValues updateValues = new ContentValues(); updateValues.putNull(Photo.PHOTO_FILE_ID); ops.add(ContentProviderOperation.newUpdate( - ContentUris.withAppendedId(Data.CONTENT_URI, id)) + ContentUris.withAppendedId(Data.CONTENT_URI, dataId)) .withValues(updateValues).build()); } + if (photoFileIdToStreamItemPhotoId.containsKey(missingPhotoId)) { + // For missing photos that were in stream item photos, just delete the stream + // item photo. + long streamItemPhotoId = photoFileIdToStreamItemPhotoId.get(missingPhotoId); + long streamItemId = streamItemPhotoIdToStreamItemId.get(streamItemPhotoId); + ops.add(ContentProviderOperation.newDelete( + StreamItems.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(streamItemId)) + .appendPath(StreamItems.StreamItemPhotos.CONTENT_DIRECTORY) + .appendPath(String.valueOf(streamItemPhotoId)) + .build()).build()); + } } try { applyBatch(ops); @@ -2330,8 +2364,16 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun Account account = resolveAccount(uri, mValues); enforceModifyingAccount(account, rawContactId); + // Don't attempt to insert accounts params - they don't exist in the stream items table. + mValues.remove(RawContacts.ACCOUNT_NAME); + mValues.remove(RawContacts.ACCOUNT_TYPE); + // Insert the new stream item. - id = mDb.insert(Tables.STREAM_ITEMS, null, values); + id = mDb.insert(Tables.STREAM_ITEMS, null, mValues); + if (id == -1) { + // Insertion failed. + return 0; + } // Check to see if we're over the limit for stream items under this raw contact. // It's possible that the inserted stream item is older than the the existing @@ -2347,7 +2389,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun * * @param uri the insertion URI * @param values the values for the new row - * @return the stream item photo _ID of the newly created row + * @return the stream item photo _ID of the newly created row, or 0 if there was an issue + * with processing the photo or creating the row */ private long insertStreamItemPhoto(Uri uri, ContentValues values) { long id = 0; @@ -2366,22 +2409,60 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun Account account = resolveAccount(uri, mValues); enforceModifyingAccount(account, rawContactId); - // Make certain that the photo doesn't exceed our maximum byte size. - byte[] photoBytes = values.getAsByteArray(StreamItemPhotos.PICTURE); - Log.i(TAG, "Inserting " + photoBytes.length + "-byte photo (max allowed " + - mMaxStreamItemPhotoSizeBytes + " bytes)"); - if (photoBytes.length > mMaxStreamItemPhotoSizeBytes) { - throw new IllegalArgumentException("Stream item photos cannot be more than " + - mMaxStreamItemPhotoSizeBytes + " bytes (received picture with " + - photoBytes.length + " bytes)"); - } + // Don't attempt to insert accounts params - they don't exist in the stream item + // photos table. + mValues.remove(RawContacts.ACCOUNT_NAME); + mValues.remove(RawContacts.ACCOUNT_TYPE); - id = mDb.insert(Tables.STREAM_ITEM_PHOTOS, null, values); + // Process the photo and store it. + if (processStreamItemPhoto(mValues, false)) { + // Insert the stream item photo. + id = mDb.insert(Tables.STREAM_ITEM_PHOTOS, null, mValues); + } } return id; } /** + * Processes the photo contained in the {@link ContactsContract.StreamItemPhotos#PHOTO} + * field of the given values, attempting to store it in the photo store. If successful, + * the resulting photo file ID will be added to the values for insert/update in the table. + * <p> + * If updating, it is valid for the picture to be empty or unspecified (the function will + * still return true). If inserting, a valid picture must be specified. + * @param values The content values provided by the caller. + * @param forUpdate Whether this photo is being processed for update (vs. insert). + * @return Whether the insert or update should proceed. + */ + private boolean processStreamItemPhoto(ContentValues values, boolean forUpdate) { + if (!values.containsKey(StreamItemPhotos.PHOTO)) { + return forUpdate; + } + byte[] photoBytes = values.getAsByteArray(StreamItemPhotos.PHOTO); + if (photoBytes == null) { + return forUpdate; + } + + // Process the photo and store it. + try { + long photoFileId = mPhotoStore.insert(new PhotoProcessor(photoBytes, + mMaxDisplayPhotoDim, mMaxThumbnailPhotoDim), true); + if (photoFileId != 0) { + values.put(StreamItemPhotos.PHOTO_FILE_ID, photoFileId); + values.remove(StreamItemPhotos.PHOTO); + return true; + } else { + // Couldn't store the photo, return 0. + Log.e(TAG, "Could not process stream item photo for insert"); + return false; + } + } catch (IOException ioe) { + Log.e(TAG, "Could not process stream item photo for insert", ioe); + return false; + } + } + + /** * Looks up the raw contact ID that owns the specified stream item. * @param streamItemId The ID of the stream item. * @return The associated raw contact ID, or -1 if no such stream item exists. @@ -2703,6 +2784,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun long rawContactId = -1; long contactId = -1; Long dataId = values.getAsLong(StatusUpdates.DATA_ID); + String accountType = null; + String accountName = null; mSb.setLength(0); mSelectionArgs.clear(); if (dataId != null) { @@ -2771,6 +2854,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun if (cursor.moveToFirst()) { dataId = cursor.getLong(DataContactsQuery.DATA_ID); rawContactId = cursor.getLong(DataContactsQuery.RAW_CONTACT_ID); + accountType = cursor.getString(DataContactsQuery.ACCOUNT_TYPE); + accountName = cursor.getString(DataContactsQuery.ACCOUNT_NAME); contactId = cursor.getLong(DataContactsQuery.CONTACT_ID); } else { // No contact found, return a null URI @@ -2825,13 +2910,59 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun if (TextUtils.isEmpty(status)) { mDbHelper.deleteStatusUpdate(dataId); - } else if (values.containsKey(StatusUpdates.STATUS_TIMESTAMP)) { - long timestamp = values.getAsLong(StatusUpdates.STATUS_TIMESTAMP); - mDbHelper.replaceStatusUpdate(dataId, timestamp, status, resPackage, iconResource, - labelResource); } else { - mDbHelper.insertStatusUpdate(dataId, status, resPackage, iconResource, - labelResource); + Long timestamp = values.getAsLong(StatusUpdates.STATUS_TIMESTAMP); + if (timestamp != null) { + mDbHelper.replaceStatusUpdate(dataId, timestamp, status, resPackage, + iconResource, labelResource); + } else { + mDbHelper.insertStatusUpdate(dataId, status, resPackage, iconResource, + labelResource); + } + + // For forward compatibility with the new stream item API, insert this status update + // there as well. If we already have a stream item from this source, update that + // one instead of inserting a new one (since the semantics of the old status update + // API is to only have a single record). + if (rawContactId != -1 && !TextUtils.isEmpty(status)) { + ContentValues streamItemValues = new ContentValues(); + streamItemValues.put(StreamItems.RAW_CONTACT_ID, rawContactId); + streamItemValues.put(StreamItems.TEXT, status); + streamItemValues.put(StreamItems.COMMENTS, ""); + streamItemValues.put(StreamItems.RES_PACKAGE, resPackage); + streamItemValues.put(StreamItems.RES_ICON, iconResource); + streamItemValues.put(StreamItems.RES_LABEL, labelResource); + streamItemValues.put(StreamItems.TIMESTAMP, + timestamp == null ? System.currentTimeMillis() : timestamp); + + // Note: The following is basically a workaround for the fact that status + // updates didn't do any sort of account enforcement, while social stream item + // updates do. We can't expect callers of the old API to start passing account + // information along, so we just populate the account params appropriately for + // the raw contact. + if (accountName != null && accountType != null) { + streamItemValues.put(RawContacts.ACCOUNT_NAME, accountName); + streamItemValues.put(RawContacts.ACCOUNT_TYPE, accountType); + } + + // Check for an existing stream item from this source, and insert or update. + Uri streamUri = StreamItems.CONTENT_URI; + Cursor c = query(streamUri, new String[]{StreamItems._ID}, + StreamItems.RAW_CONTACT_ID + "=?", + new String[]{String.valueOf(rawContactId)}, null); + try { + if (c.getCount() > 0) { + c.moveToFirst(); + update(ContentUris.withAppendedId(streamUri, c.getLong(0)), + streamItemValues, null, null); + } else { + insert(streamUri, streamItemValues); + } + } finally { + c.close(); + } + + } } } @@ -3398,6 +3529,10 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun Account account = resolveAccount(uri, values); enforceModifyingAccountForStreamItems(account, selection, selectionArgs); + // Don't attempt to update accounts params - they don't exist in the stream items table. + values.remove(RawContacts.ACCOUNT_NAME); + values.remove(RawContacts.ACCOUNT_TYPE); + // If there's been no exception, the update should be fine. return mDb.update(Tables.STREAM_ITEMS, values, selection, selectionArgs); } @@ -3411,8 +3546,17 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun Account account = resolveAccount(uri, values); enforceModifyingAccountForStreamItemPhotos(account, selection, selectionArgs); - // If there's been no exception, the update should be fine. - return mDb.update(Tables.STREAM_ITEM_PHOTOS, values, selection, selectionArgs); + // Don't attempt to update accounts params - they don't exist in the stream item + // photos table. + values.remove(RawContacts.ACCOUNT_NAME); + values.remove(RawContacts.ACCOUNT_TYPE); + + // Process the photo (since we're updating, it's valid for the photo to not be present). + if (processStreamItemPhoto(values, true)) { + // If there's been no exception, the update should be fine. + return mDb.update(Tables.STREAM_ITEM_PHOTOS, values, selection, selectionArgs); + } + return 0; } /** @@ -4507,13 +4651,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun } case STREAM_ITEMS_LIMIT: { - MatrixCursor cursor = new MatrixCursor( - new String[]{StreamItems.MAX_ITEMS, StreamItems.PHOTO_MAX_BYTES}, 1); - cursor.addRow( - new Object[]{ - MAX_STREAM_ITEMS_PER_RAW_CONTACT, - mMaxStreamItemPhotoSizeBytes - }); + MatrixCursor cursor = new MatrixCursor(new String[]{StreamItems.MAX_ITEMS}, 1); + cursor.addRow(new Object[]{MAX_STREAM_ITEMS_PER_RAW_CONTACT}); return cursor; } diff --git a/src/com/android/providers/contacts/PhotoStore.java b/src/com/android/providers/contacts/PhotoStore.java index 1ed925d..549cea0 100644 --- a/src/com/android/providers/contacts/PhotoStore.java +++ b/src/com/android/providers/contacts/PhotoStore.java @@ -161,13 +161,27 @@ public class PhotoStore { * is thumbnail-sized or smaller. */ public synchronized long insert(PhotoProcessor photoProcessor) { + return insert(photoProcessor, false); + } + + /** + * Inserts the photo in the given photo processor into the photo store. If the display photo + * is already thumbnail-sized or smaller, this will do nothing (and will return 0) unless + * allowSmallImageStorage is specified. + * @param photoProcessor A photo processor containing the photo data to insert. + * @param allowSmallImageStorage Whether thumbnail-sized or smaller photos should still be + * stored in the file store. + * @return The photo file ID associated with the file, or 0 if the file could not be created or + * is thumbnail-sized or smaller and allowSmallImageStorage is false. + */ + public synchronized long insert(PhotoProcessor photoProcessor, boolean allowSmallImageStorage) { Bitmap displayPhoto = photoProcessor.getDisplayPhoto(); int width = displayPhoto.getWidth(); int height = displayPhoto.getHeight(); int thumbnailDim = photoProcessor.getMaxThumbnailPhotoDim(); - if (width > thumbnailDim || height > thumbnailDim) { - // The display photo is larger than a thumbnail, so write the photo to a temp file, - // create the DB record for tracking it, and rename the temp file to match. + if (allowSmallImageStorage || width > thumbnailDim || height > thumbnailDim) { + // Write the photo to a temp file, create the DB record for tracking it, and rename the + // temp file to match. File file = null; try { // Write the display photo to a temp file. diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java index 597b975..df83373 100644 --- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java +++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java @@ -2987,6 +2987,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { ContentValues photo1Values = buildGenericStreamItemPhotoValues(1); insertStreamItemPhoto(streamItemId, photo1Values, null); + photo1Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. ContentValues photo2Values = buildGenericStreamItemPhotoValues(2); insertStreamItemPhoto(streamItemId, photo2Values, null); @@ -3011,11 +3012,14 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { // Add a photo to the first stream item. ContentValues photo1Values = buildGenericStreamItemPhotoValues(1); insertStreamItemPhoto(firstStreamItemId, photo1Values, null); + photo1Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. // Add a photo to the second stream item. ContentValues photo2Values = buildGenericStreamItemPhotoValues(1); - photo2Values.put(StreamItemPhotos.PICTURE, "Some other picture".getBytes()); + photo2Values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( + R.drawable.nebula, PhotoSize.ORIGINAL)); insertStreamItemPhoto(secondStreamItemId, photo2Values, null); + photo2Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. // Select only the photos from the second stream item. assertStoredValues(Uri.withAppendedPath( @@ -3040,12 +3044,15 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { ContentValues photo1Values = buildGenericStreamItemPhotoValues(1); resultUri = insertStreamItemPhoto(firstStreamItemId, photo1Values, null); long firstPhotoId = ContentUris.parseId(resultUri); + photo1Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. // Add a photo to the second stream item. ContentValues photo2Values = buildGenericStreamItemPhotoValues(1); - photo2Values.put(StreamItemPhotos.PICTURE, "Some other picture".getBytes()); + photo2Values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( + R.drawable.galaxy, PhotoSize.ORIGINAL)); resultUri = insertStreamItemPhoto(secondStreamItemId, photo2Values, null); long secondPhotoId = ContentUris.parseId(resultUri); + photo2Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. // Select the first photo. assertStoredValues(ContentUris.withAppendedId( @@ -3165,25 +3172,6 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { // Stream item photo insertion test cases. - public void testInsertOversizedPhoto() { - long rawContactId = createRawContact(); - ContentValues values = buildGenericStreamItemValues(); - values.put(StreamItems.RAW_CONTACT_ID, rawContactId); - Uri resultUri = insertStreamItem(rawContactId, values, null); - long streamItemId = ContentUris.parseId(resultUri); - - // Add a huge photo to the stream item. - ContentValues photoValues = buildGenericStreamItemPhotoValues(1); - byte[] photoBytes = new byte[(70 * 1024) + 1]; - photoValues.put(StreamItemPhotos.PICTURE, photoBytes); - try { - insertStreamItemPhoto(streamItemId, photoValues, null); - fail("Should have failed due to image size"); - } catch (IllegalArgumentException expected) { - // Huzzah! - } - } - public void testInsertStreamItemsAndPhotosInBatch() throws Exception { long rawContactId = createRawContact(); ContentValues streamItemValues = buildGenericStreamItemValues(); @@ -3219,10 +3207,18 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { c = mResolver.query(Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), - StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), new String[]{StreamItemPhotos._ID}, + StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), + new String[]{StreamItemPhotos._ID, StreamItemPhotos.PHOTO_URI}, null, null, null); try { assertEquals(5, c.getCount()); + byte[] expectedPhotoBytes = loadPhotoFromResource( + R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO); + while (c.moveToNext()) { + String photoUri = c.getString(1); + assertInputStreamContent(expectedPhotoBytes, + mResolver.openInputStream(Uri.parse(photoUri))); + } } finally { c.close(); } @@ -3274,7 +3270,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { // Stream item photo update test cases. - public void testUpdateStreamItemPhotoById() { + public void testUpdateStreamItemPhotoById() throws IOException { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, values, null); @@ -3283,7 +3279,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { resultUri = insertStreamItemPhoto(streamItemId, photoValues, null); long streamItemPhotoId = ContentUris.parseId(resultUri); - photoValues.put(StreamItemPhotos.PICTURE, "ABCDEFG".getBytes()); + photoValues.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( + R.drawable.nebula, PhotoSize.ORIGINAL)); Uri photoUri = ContentUris.withAppendedId( Uri.withAppendedPath( @@ -3291,10 +3288,16 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), streamItemPhotoId); mResolver.update(photoUri, photoValues, null, null); + photoValues.remove(StreamItemPhotos.PHOTO); // Removed during processing. assertStoredValues(photoUri, photoValues); + + // Check that the photo stored is the expected one. + String displayPhotoUri = getStoredValue(photoUri, StreamItemPhotos.PHOTO_URI); + assertInputStreamContent(loadPhotoFromResource(R.drawable.nebula, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(Uri.parse(displayPhotoUri))); } - public void testUpdateStreamItemPhotoWithContentValues() { + public void testUpdateStreamItemPhotoWithContentValues() throws IOException { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, values, null); @@ -3304,13 +3307,20 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { long streamItemPhotoId = ContentUris.parseId(resultUri); photoValues.put(StreamItemPhotos._ID, streamItemPhotoId); - photoValues.put(StreamItemPhotos.PICTURE, "ABCDEFG".getBytes()); + photoValues.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( + R.drawable.nebula, PhotoSize.ORIGINAL)); Uri photoUri = Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY); mResolver.update(photoUri, photoValues, null, null); + photoValues.remove(StreamItemPhotos.PHOTO); // Removed during processing. assertStoredValues(photoUri, photoValues); + + // Check that the photo stored is the expected one. + String displayPhotoUri = getStoredValue(photoUri, StreamItemPhotos.PHOTO_URI); + assertInputStreamContent(loadPhotoFromResource(R.drawable.nebula, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(Uri.parse(displayPhotoUri))); } public void testUpdateStreamItemPhotoFromOtherAccount() { @@ -3323,7 +3333,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { long streamItemPhotoId = ContentUris.parseId(resultUri); photoValues.put(StreamItemPhotos._ID, streamItemPhotoId); - photoValues.put(StreamItemPhotos.PICTURE, "ABCDEFG".getBytes()); + photoValues.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( + R.drawable.galaxy, PhotoSize.ORIGINAL)); Uri photoUri = maybeAddAccountQueryParameters( Uri.withAppendedPath( @@ -3427,6 +3438,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { ContentValues firstPhotoValues = buildGenericStreamItemPhotoValues(0); ContentValues secondPhotoValues = buildGenericStreamItemPhotoValues(1); insertStreamItemPhoto(streamItemId, firstPhotoValues, null); + firstPhotoValues.remove(StreamItemPhotos.PHOTO); // Removed while processing. insertStreamItemPhoto(streamItemId, secondPhotoValues, null); Uri photoUri = Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), @@ -3456,13 +3468,56 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { public void testQueryStreamItemLimit() { ContentValues values = new ContentValues(); values.put(StreamItems.MAX_ITEMS, 5); - values.put(StreamItems.PHOTO_MAX_BYTES, 70 * 1024); assertStoredValues(StreamItems.CONTENT_LIMIT_URI, values); } + // Tests for inserting or updating stream items as a side-effect of making status updates + // (forward-compatibility of status updates into the new social stream API). + + public void testStreamItemInsertedOnStatusUpdate() { + + // This method of creating a raw contact automatically inserts a status update with + // the status message "hacking". + ContentValues values = new ContentValues(); + long rawContactId = createRawContact(values, "18004664411", + "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, + StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO | + StatusUpdates.CAPABILITY_HAS_VOICE); + + ContentValues expectedValues = new ContentValues(); + expectedValues.put(StreamItems.RAW_CONTACT_ID, rawContactId); + expectedValues.put(StreamItems.TEXT, "hacking"); + assertStoredValues(RawContacts.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(rawContactId)) + .appendPath(RawContacts.StreamItems.CONTENT_DIRECTORY).build(), + expectedValues); + } + + public void testStreamItemUpdatedOnSecondStatusUpdate() { + + // This method of creating a raw contact automatically inserts a status update with + // the status message "hacking". + ContentValues values = new ContentValues(); + int chatMode = StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO | + StatusUpdates.CAPABILITY_HAS_VOICE; + long rawContactId = createRawContact(values, "18004664411", + "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, chatMode); + + // Insert a new status update for the raw contact. + insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "goog411@acme.com", + StatusUpdates.INVISIBLE, "finished hacking", chatMode); + + ContentValues expectedValues = new ContentValues(); + expectedValues.put(StreamItems.RAW_CONTACT_ID, rawContactId); + expectedValues.put(StreamItems.TEXT, "finished hacking"); + assertStoredValues(RawContacts.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(rawContactId)) + .appendPath(RawContacts.StreamItems.CONTENT_DIRECTORY).build(), + expectedValues); + } + private ContentValues buildGenericStreamItemValues() { ContentValues values = new ContentValues(); - values.put(StreamItems.RES_PACKAGE, "com.foo.bar"); values.put(StreamItems.TEXT, "Hello world"); values.put(StreamItems.TIMESTAMP, System.currentTimeMillis()); values.put(StreamItems.COMMENTS, "Reshared by 123 others"); @@ -3472,7 +3527,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { private ContentValues buildGenericStreamItemPhotoValues(int sortIndex) { ContentValues values = new ContentValues(); values.put(StreamItemPhotos.SORT_INDEX, sortIndex); - values.put(StreamItemPhotos.PICTURE, "DEADBEEF".getBytes()); + values.put(StreamItemPhotos.PHOTO, + loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.ORIGINAL)); return values; } |