diff options
Diffstat (limited to 'tests/src/com/android/providers')
7 files changed, 684 insertions, 49 deletions
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java index 50cd50e..ea99caf 100644 --- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java +++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java @@ -16,8 +16,7 @@ package com.android.providers.contacts; -import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY; - +import com.google.android.collect.Maps; import com.google.android.collect.Sets; import android.accounts.Account; @@ -50,7 +49,6 @@ import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.Settings; import android.provider.ContactsContract.StatusUpdates; import android.provider.ContactsContract.StreamItems; -import android.provider.ContactsContract.StreamItemPhotos; import android.test.AndroidTestCase; import android.test.MoreAsserts; import android.test.mock.MockContentResolver; @@ -67,10 +65,12 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY; + /** * A common superclass for {@link ContactsProvider2}-related tests. */ -public abstract class BaseContactsProvider2Test extends AndroidTestCase { +public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase { protected static final String PACKAGE = "ContactsProvider2Test"; public static final String READ_ONLY_ACCOUNT_TYPE = @@ -81,8 +81,6 @@ public abstract class BaseContactsProvider2Test extends AndroidTestCase { protected Account mAccount = new Account("account1", "account type1"); protected Account mAccountTwo = new Account("account2", "account type2"); - private byte[] mTestPhoto; - protected final static Long NO_LONG = new Long(0); protected final static String NO_STRING = new String(""); protected final static Account NO_ACCOUNT = new Account("a", "b"); @@ -363,6 +361,15 @@ public abstract class BaseContactsProvider2Test extends AndroidTestCase { return resultUri; } + protected Uri insertPhoto(long rawContactId, int resourceId) { + ContentValues values = new ContentValues(); + values.put(Data.RAW_CONTACT_ID, rawContactId); + values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); + values.put(Photo.PHOTO, loadPhotoFromResource(resourceId, PhotoSize.ORIGINAL)); + Uri resultUri = mResolver.insert(Data.CONTENT_URI, values); + return resultUri; + } + protected Uri insertGroupMembership(long rawContactId, String sourceId) { ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId); @@ -830,6 +837,10 @@ public abstract class BaseContactsProvider2Test extends AndroidTestCase { return value; } + protected Long getStoredLongValue(Uri uri, String column) { + return getStoredLongValue(uri, null, null, column); + } + protected void assertStoredValues(Uri rowUri, ContentValues expectedValues) { assertStoredValues(rowUri, null, null, expectedValues); } @@ -1025,26 +1036,6 @@ public abstract class BaseContactsProvider2Test extends AndroidTestCase { } } - protected byte[] loadTestPhoto() { - if (mTestPhoto == null) { - final Resources resources = getContext().getResources(); - InputStream is = resources - .openRawResource(com.android.internal.R.drawable.ic_contact_picture); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - byte[] buffer = new byte[1000]; - int count; - try { - while ((count = is.read(buffer)) != -1) { - os.write(buffer, 0, count); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - mTestPhoto = os.toByteArray(); - } - return mTestPhoto; - } - public static void dump(ContentResolver resolver, boolean aggregatedOnly) { String[] projection = new String[] { Contacts._ID, diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java index f22943e..865c956 100644 --- a/tests/src/com/android/providers/contacts/ContactsActor.java +++ b/tests/src/com/android/providers/contacts/ContactsActor.java @@ -59,6 +59,7 @@ import android.test.mock.MockContext; import android.test.mock.MockResources; import android.util.TypedValue; +import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Locale; @@ -152,6 +153,12 @@ public class ContactsActor { mProviderContext = new IsolatedContext(resolver, targetContextWrapper){ @Override + public File getFilesDir() { + // TODO: Need to figure out something more graceful than this. + return new File("/data/data/com.android.providers.contacts.tests/files"); + } + + @Override public Object getSystemService(String name) { if (Context.COUNTRY_DETECTOR.equals(name)) { return mMockCountryDetector; diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java index ab43158..aa19e4e 100644 --- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java +++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java @@ -19,6 +19,7 @@ package com.android.providers.contacts; import com.android.internal.util.ArrayUtils; import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns; import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns; +import com.android.providers.contacts.tests.R; import com.google.android.collect.Lists; import android.accounts.Account; @@ -47,6 +48,7 @@ import android.provider.ContactsContract.Data; import android.provider.ContactsContract.DataUsageFeedback; import android.provider.ContactsContract.Directory; import android.provider.ContactsContract.DisplayNameSources; +import android.provider.ContactsContract.DisplayPhoto; import android.provider.ContactsContract.FullNameStyle; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.PhoneLookup; @@ -58,16 +60,18 @@ import android.provider.ContactsContract.RawContactsEntity; import android.provider.ContactsContract.SearchSnippetColumns; import android.provider.ContactsContract.Settings; import android.provider.ContactsContract.StatusUpdates; -import android.provider.ContactsContract.StreamItems; import android.provider.ContactsContract.StreamItemPhotos; +import android.provider.ContactsContract.StreamItems; import android.provider.LiveFolders; import android.provider.OpenableColumns; import android.test.MoreAsserts; import android.test.suitebuilder.annotation.LargeTest; +import android.text.TextUtils; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; @@ -104,6 +108,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, + Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, @@ -138,6 +143,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, + Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, @@ -246,6 +252,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, + Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, @@ -314,6 +321,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, + Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.HAS_PHONE_NUMBER, @@ -395,6 +403,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, + Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, @@ -4012,28 +4021,29 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "John", "Doe"); - Uri photoUri = insertPhoto(rawContactId); - - Uri twigUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, - queryContactId(rawContactId)), Contacts.Photo.CONTENT_DIRECTORY); + long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal)); + long photoFileId = getStoredLongValue(Data.CONTENT_URI, Data._ID + "=?", + new String[]{String.valueOf(dataId)}, Photo.PHOTO_FILE_ID); + String photoUri = ContentUris.withAppendedId(DisplayPhoto.CONTENT_URI, photoFileId) + .toString(); assertStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)), - Contacts.PHOTO_URI, twigUri.toString()); - - long twigId = Long.parseLong(getStoredValue(twigUri, Data._ID)); - assertEquals(ContentUris.parseId(photoUri), twigId); + Contacts.PHOTO_URI, photoUri); } public void testInputStreamForPhoto() throws Exception { long rawContactId = createRawContact(); - Uri photoUri = insertPhoto(rawContactId); - assertInputStreamContent(loadTestPhoto(), mResolver.openInputStream(photoUri)); + long contactId = queryContactId(rawContactId); + Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + insertPhoto(rawContactId); + Uri photoUri = Uri.parse(getStoredValue(contactUri, Contacts.PHOTO_URI)); + Uri photoThumbnailUri = Uri.parse(getStoredValue(contactUri, Contacts.PHOTO_THUMBNAIL_URI)); - Uri contactPhotoUri = Uri.withAppendedPath( - ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)), - Contacts.Photo.CONTENT_DIRECTORY); - assertInputStreamContent(loadTestPhoto(), mResolver.openInputStream(contactPhotoUri)); + assertInputStreamContent(loadTestPhoto(PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(photoUri)); + assertInputStreamContent(loadTestPhoto(PhotoSize.THUMBNAIL), + mResolver.openInputStream(photoThumbnailUri)); } private static void assertInputStreamContent(byte[] expected, InputStream is) @@ -4051,11 +4061,11 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { public void testSuperPrimaryPhoto() { long rawContactId1 = createRawContact(new Account("a", "a")); - Uri photoUri1 = insertPhoto(rawContactId1); + Uri photoUri1 = insertPhoto(rawContactId1, R.drawable.earth_normal); long photoId1 = ContentUris.parseId(photoUri1); long rawContactId2 = createRawContact(new Account("b", "b")); - Uri photoUri2 = insertPhoto(rawContactId2); + Uri photoUri2 = insertPhoto(rawContactId2, R.drawable.earth_normal); long photoId2 = ContentUris.parseId(photoUri2); setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, @@ -4063,9 +4073,13 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId1)); + + long photoFileId1 = getStoredLongValue(Data.CONTENT_URI, Data._ID + "=?", + new String[]{String.valueOf(photoId1)}, Photo.PHOTO_FILE_ID); + String photoUri = ContentUris.withAppendedId(DisplayPhoto.CONTENT_URI, photoFileId1) + .toString(); assertStoredValue(contactUri, Contacts.PHOTO_ID, photoId1); - assertStoredValue(contactUri, Contacts.PHOTO_URI, - Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY)); + assertStoredValue(contactUri, Contacts.PHOTO_URI, photoUri); setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, rawContactId1, rawContactId2); @@ -4107,7 +4121,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { mResolver.update(dataUri, values, null, null); assertNetworkNotified(true); - long twigId = Long.parseLong(getStoredValue(twigUri, Data._ID)); + long twigId = getStoredLongValue(twigUri, Data._ID); assertEquals(photoId, twigId); } @@ -4142,10 +4156,313 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Cursor storedPhoto = mResolver.query(dataUri, new String[] {Photo.PHOTO}, Data.MIMETYPE + "=?", new String[] {Photo.CONTENT_ITEM_TYPE}, null); storedPhoto.moveToFirst(); - MoreAsserts.assertEquals(loadTestPhoto(), storedPhoto.getBlob(0)); + MoreAsserts.assertEquals(loadTestPhoto(PhotoSize.THUMBNAIL), storedPhoto.getBlob(0)); storedPhoto.close(); } + public void testOpenDisplayPhotoForContactId() throws IOException { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + insertPhoto(rawContactId, R.drawable.earth_normal); + Uri photoUri = Contacts.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(contactId)) + .appendPath(Contacts.Photo.DISPLAY_PHOTO).build(); + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(photoUri)); + } + + public void testOpenDisplayPhotoForContactLookupKey() throws IOException { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + String lookupKey = queryLookupKey(contactId); + insertPhoto(rawContactId, R.drawable.earth_normal); + Uri photoUri = Contacts.CONTENT_LOOKUP_URI.buildUpon() + .appendPath(lookupKey) + .appendPath(Contacts.Photo.DISPLAY_PHOTO).build(); + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(photoUri)); + } + + public void testOpenDisplayPhotoForContactLookupKeyAndId() throws IOException { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + String lookupKey = queryLookupKey(contactId); + insertPhoto(rawContactId, R.drawable.earth_normal); + Uri photoUri = Contacts.CONTENT_LOOKUP_URI.buildUpon() + .appendPath(lookupKey) + .appendPath(String.valueOf(contactId)) + .appendPath(Contacts.Photo.DISPLAY_PHOTO).build(); + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(photoUri)); + } + + public void testOpenDisplayPhotoForRawContactId() throws IOException { + long rawContactId = createRawContactWithName(); + insertPhoto(rawContactId, R.drawable.earth_normal); + Uri photoUri = RawContacts.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(rawContactId)) + .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build(); + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(photoUri)); + } + + public void testOpenDisplayPhotoByPhotoUri() throws IOException { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + insertPhoto(rawContactId, R.drawable.earth_normal); + + // Get the photo URI out and check the content. + String photoUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), + Contacts.PHOTO_URI); + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(Uri.parse(photoUri))); + } + + public void testPhotoUriForDisplayPhoto() { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + + // Photo being inserted is larger than a thumbnail, so it will be stored as a file. + long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal)); + String photoFileId = getStoredValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), + Photo.PHOTO_FILE_ID); + String photoUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), + Contacts.PHOTO_URI); + + // Check that the photo URI differs from the thumbnail. + String thumbnailUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), + Contacts.PHOTO_THUMBNAIL_URI); + assertFalse(photoUri.equals(thumbnailUri)); + + // URI should be of the form display_photo/ID + assertEquals(Uri.withAppendedPath(DisplayPhoto.CONTENT_URI, photoFileId).toString(), + photoUri); + } + + public void testPhotoUriForThumbnailPhoto() throws IOException { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + + // Photo being inserted is a thumbnail, so it will only be stored in a BLOB. The photo URI + // will fall back to the thumbnail URI. + insertPhoto(rawContactId, R.drawable.earth_small); + String photoUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), + Contacts.PHOTO_URI); + + // Check that the photo URI is equal to the thumbnail URI. + String thumbnailUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), + Contacts.PHOTO_THUMBNAIL_URI); + assertEquals(photoUri, thumbnailUri); + + // URI should be of the form contacts/ID/photo + assertEquals(Uri.withAppendedPath( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), + Contacts.Photo.CONTENT_DIRECTORY).toString(), + photoUri); + + // Loading the photo URI content should get the thumbnail. + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL), + mResolver.openInputStream(Uri.parse(photoUri))); + } + + public void testWriteNewPhotoToAssetFile() throws IOException { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + + // Load in a huge photo. + byte[] originalPhoto = loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.ORIGINAL); + + // Write it out. + Uri writeablePhotoUri = RawContacts.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(rawContactId)) + .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build(); + OutputStream os = mResolver.openOutputStream(writeablePhotoUri, "rw"); + try { + os.write(originalPhoto); + } finally { + os.close(); + } + + // Check that the display photo and thumbnail have been set. + String photoUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); + assertFalse(TextUtils.isEmpty(photoUri)); + String thumbnailUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), + Contacts.PHOTO_THUMBNAIL_URI); + assertFalse(TextUtils.isEmpty(thumbnailUri)); + assertFalse(photoUri.equals(thumbnailUri)); + + // Check the content of the display photo and thumbnail. + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(Uri.parse(photoUri))); + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.THUMBNAIL), + mResolver.openInputStream(Uri.parse(thumbnailUri))); + } + + public void testWriteUpdatedPhotoToAssetFile() throws IOException { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + + // Insert a large photo first. + insertPhoto(rawContactId, R.drawable.earth_large); + String largeEarthPhotoUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); + + // Load in a huge photo. + byte[] originalPhoto = loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.ORIGINAL); + + // Write it out. + Uri writeablePhotoUri = RawContacts.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(rawContactId)) + .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build(); + OutputStream os = mResolver.openOutputStream(writeablePhotoUri, "rw"); + try { + os.write(originalPhoto); + } finally { + os.close(); + } + + // Check that the display photo URI has been modified. + String hugeEarthPhotoUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); + assertFalse(hugeEarthPhotoUri.equals(largeEarthPhotoUri)); + + // Check the content of the display photo and thumbnail. + String hugeEarthThumbnailUri = getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), + Contacts.PHOTO_THUMBNAIL_URI); + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.DISPLAY_PHOTO), + mResolver.openInputStream(Uri.parse(hugeEarthPhotoUri))); + assertInputStreamContent( + loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.THUMBNAIL), + mResolver.openInputStream(Uri.parse(hugeEarthThumbnailUri))); + + } + + public void testPhotoDimensionLimits() { + ContentValues values = new ContentValues(); + values.put(DisplayPhoto.DISPLAY_MAX_DIM, 256); + values.put(DisplayPhoto.THUMBNAIL_MAX_DIM, 96); + assertStoredValues(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, values); + } + + public void testPhotoStoreCleanup() throws IOException { + SynchronousContactsProvider2 provider = (SynchronousContactsProvider2) mActor.provider; + + // Trigger an initial cleanup so another one won't happen while we're running this test. + provider.cleanupPhotoStore(); + + // Insert a couple of contacts with photos. + long rawContactId1 = createRawContactWithName(); + long contactId1 = queryContactId(rawContactId1); + long dataId1 = ContentUris.parseId(insertPhoto(rawContactId1, R.drawable.earth_normal)); + long photoFileId1 = + getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId1), + Photo.PHOTO_FILE_ID); + + long rawContactId2 = createRawContactWithName(); + long contactId2 = queryContactId(rawContactId2); + long dataId2 = ContentUris.parseId(insertPhoto(rawContactId2, R.drawable.earth_normal)); + long photoFileId2 = + getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId2), + Photo.PHOTO_FILE_ID); + + // Update the second raw contact with a different photo. + ContentValues values = new ContentValues(); + values.put(Data.RAW_CONTACT_ID, rawContactId2); + values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); + values.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.ORIGINAL)); + assertEquals(1, mResolver.update(Data.CONTENT_URI, values, Data._ID + "=?", + new String[]{String.valueOf(dataId2)})); + long replacementPhotoFileId = + getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId2), + Photo.PHOTO_FILE_ID); + + // Insert a third raw contact that has a bogus photo file ID. + long bogusFileId = 1234567; + long rawContactId3 = createRawContactWithName(); + long contactId3 = queryContactId(rawContactId3); + values.clear(); + values.put(Data.RAW_CONTACT_ID, rawContactId3); + values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); + values.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_normal, + PhotoSize.THUMBNAIL)); + values.put(Photo.PHOTO_FILE_ID, bogusFileId); + values.put(DataRowHandlerForPhoto.SKIP_PROCESSING_KEY, true); + mResolver.insert(Data.CONTENT_URI, values); + + // Also insert a bogus photo that nobody is using. + PhotoStore photoStore = provider.getPhotoStore(); + long bogusPhotoId = photoStore.insert(new PhotoProcessor(loadPhotoFromResource( + R.drawable.earth_huge, PhotoSize.ORIGINAL), 256, 96)); + + // Manually trigger another cleanup in the provider. + provider.cleanupPhotoStore(); + + // The following things should have happened. + + // 1. Raw contact 1 and its photo remain unaffected. + assertEquals(photoFileId1, (long) getStoredLongValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1), + Contacts.PHOTO_FILE_ID)); + + // 2. Raw contact 2 retains its new photo. The old one is deleted from the photo store. + assertEquals(replacementPhotoFileId, (long) getStoredLongValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId2), + Contacts.PHOTO_FILE_ID)); + assertNull(photoStore.get(photoFileId2)); + + // 3. Raw contact 3 should have its photo file reference cleared. + assertNull(getStoredValue( + ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId3), + Contacts.PHOTO_FILE_ID)); + + // 4. The bogus photo that nobody was using should be cleared from the photo store. + assertNull(photoStore.get(bogusPhotoId)); + } + + public void testOverwritePhotoWithThumbnail() throws IOException { + long rawContactId = createRawContactWithName(); + long contactId = queryContactId(rawContactId); + Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + + // Write a regular-size photo. + long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal)); + Long photoFileId = getStoredLongValue(contactUri, Contacts.PHOTO_FILE_ID); + assertTrue(photoFileId != null && photoFileId > 0); + + // Now overwrite the photo with a thumbnail-sized photo. + ContentValues update = new ContentValues(); + update.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_small, PhotoSize.ORIGINAL)); + mResolver.update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), update, null, null); + + // Photo file ID should have been nulled out, and the photo URI should be the same as the + // thumbnail URI. + assertNull(getStoredValue(contactUri, Contacts.PHOTO_FILE_ID)); + String photoUri = getStoredValue(contactUri, Contacts.PHOTO_URI); + String thumbnailUri = getStoredValue(contactUri, Contacts.PHOTO_THUMBNAIL_URI); + assertEquals(photoUri, thumbnailUri); + + // Retrieving the photo URI should get the thumbnail content. + assertInputStreamContent(loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL), + mResolver.openInputStream(Uri.parse(photoUri))); + } + public void testUpdateRawContactSetStarred() { long rawContactId1 = createRawContactWithName(); Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1); diff --git a/tests/src/com/android/providers/contacts/LegacyContactImporterPerformanceTest.java b/tests/src/com/android/providers/contacts/LegacyContactImporterPerformanceTest.java index 7e4b39f..d78193b 100644 --- a/tests/src/com/android/providers/contacts/LegacyContactImporterPerformanceTest.java +++ b/tests/src/com/android/providers/contacts/LegacyContactImporterPerformanceTest.java @@ -32,6 +32,8 @@ import android.test.mock.MockContext; import android.test.suitebuilder.annotation.MediumTest; import android.util.Log; +import java.io.File; + /** * Performance test for {@link ContactAggregator}. Run the test like this: * <code> @@ -94,7 +96,13 @@ public class LegacyContactImporterPerformanceTest extends AndroidTestCase { RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context, targetContext, "perf_imp."); targetContextWrapper.makeExistingFilesAndDbsAccessible(); - IsolatedContext providerContext = new IsolatedContext(resolver, targetContextWrapper); + IsolatedContext providerContext = new IsolatedContext(resolver, targetContextWrapper) { + @Override + public File getFilesDir() { + // TODO: Need to figure out something more graceful than this. + return new File("/data/data/com.android.providers.contacts.tests/files"); + } + }; SynchronousContactsProvider2 provider = new SynchronousContactsProvider2(); provider.setDataWipeEnabled(false); provider.attachInfo(providerContext, null); diff --git a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java index e034696..e515af2 100644 --- a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java +++ b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java @@ -16,6 +16,8 @@ package com.android.providers.contacts; +import com.android.providers.contacts.tests.*; + import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentUris; @@ -707,7 +709,10 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test { } public void testPhotoUpdate() throws Exception { - byte[] photo = loadTestPhoto(); + byte[] photo = loadPhotoFromResource( + com.android.providers.contacts.tests.R.drawable.earth_small, PhotoSize.ORIGINAL); + byte[] thumbnailedPhoto = loadPhotoFromResource( + com.android.providers.contacts.tests.R.drawable.earth_small, PhotoSize.THUMBNAIL); ContentValues values = new ContentValues(); Uri personUri = mResolver.insert(People.CONTENT_URI, values); @@ -722,13 +727,16 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test { Uri photoUri = Uri.withAppendedPath(personUri, Photos.CONTENT_DIRECTORY); mResolver.update(photoUri, values, null, null); + values.put(Photos.DATA, thumbnailedPhoto); assertStoredValues(photoUri, values); long photoId = Long.parseLong(getStoredValue(photoUri, Photos._ID)); values.put(Photos.LOCAL_VERSION, "11"); + values.put(Photos.DATA, photo); Uri contentUri = ContentUris.withAppendedId(Photos.CONTENT_URI, photoId); mResolver.update(contentUri, values, null, null); + values.put(Photos.DATA, thumbnailedPhoto); assertStoredValues(contentUri, values); assertStoredValues(photoUri, values); } diff --git a/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java b/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java new file mode 100644 index 0000000..285378c --- /dev/null +++ b/tests/src/com/android/providers/contacts/PhotoLoadingTestCase.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.contacts; + +import com.google.android.collect.Maps; + +import android.content.res.Resources; +import android.test.AndroidTestCase; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** + * Adds support for loading photo files easily from test resources. + */ +public class PhotoLoadingTestCase extends AndroidTestCase { + + private Map<Integer, PhotoEntry> photoResourceCache = Maps.newHashMap(); + protected static enum PhotoSize { + ORIGINAL, + DISPLAY_PHOTO, + THUMBNAIL + } + + protected final class PhotoEntry { + Map<PhotoSize, byte[]> photoMap = Maps.newHashMap(); + public PhotoEntry(byte[] original) { + try { + Resources resources = getContext().getResources(); + PhotoProcessor processor = new PhotoProcessor(original, + resources.getInteger(R.integer.config_max_display_photo_dim), + resources.getInteger(R.integer.config_max_thumbnail_photo_dim)); + photoMap.put(PhotoSize.ORIGINAL, original); + photoMap.put(PhotoSize.DISPLAY_PHOTO, processor.getDisplayPhotoBytes()); + photoMap.put(PhotoSize.THUMBNAIL, processor.getThumbnailPhotoBytes()); + } catch (IOException ignored) { + // Test is probably going to fail as a result anyway. + } + } + + public byte[] getPhoto(PhotoSize size) { + return photoMap.get(size); + } + } + + // The test photo will be loaded frequently in tests, so we'll just process it once. + private static PhotoEntry testPhotoEntry; + + + protected byte[] loadTestPhoto() { + int testPhotoId = com.android.providers.contacts.tests.R.drawable.ic_contact_picture; + if (testPhotoEntry == null) { + loadPhotoFromResource(testPhotoId, PhotoSize.ORIGINAL); + testPhotoEntry = photoResourceCache.get(testPhotoId); + } + return testPhotoEntry.getPhoto(PhotoSize.ORIGINAL); + } + + protected byte[] loadTestPhoto(PhotoSize size) { + loadTestPhoto(); + return testPhotoEntry.getPhoto(size); + } + + protected byte[] loadPhotoFromResource(int resourceId, PhotoSize size) { + PhotoEntry entry = photoResourceCache.get(resourceId); + if (entry == null) { + final Resources resources = getTestContext().getResources(); + InputStream is = resources.openRawResource(resourceId); + byte[] content = readInputStreamFully(is); + entry = new PhotoEntry(content); + photoResourceCache.put(resourceId, entry); + } + return entry.getPhoto(size); + } + + protected byte[] readInputStreamFully(InputStream is) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + byte[] buffer = new byte[10000]; + int count; + try { + while ((count = is.read(buffer)) != -1) { + os.write(buffer, 0, count); + } + is.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return os.toByteArray(); + } +} diff --git a/tests/src/com/android/providers/contacts/PhotoStoreTest.java b/tests/src/com/android/providers/contacts/PhotoStoreTest.java new file mode 100644 index 0000000..9b7c50d --- /dev/null +++ b/tests/src/com/android/providers/contacts/PhotoStoreTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.contacts; + +import com.android.providers.contacts.ContactsDatabaseHelper.Tables; +import com.android.providers.contacts.tests.R; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.provider.ContactsContract; +import android.provider.ContactsContract.PhotoFiles; +import android.test.mock.MockContentResolver; +import android.test.suitebuilder.annotation.LargeTest; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY; + +/** + * Tests for {@link PhotoStore}. + */ +@LargeTest +public class PhotoStoreTest extends PhotoLoadingTestCase { + + private ContactsActor mActor; + private SynchronousContactsProvider2 mProvider; + private SQLiteDatabase mDb; + + // The object under test. + private PhotoStore mPhotoStore; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mActor = new ContactsActor(getContext(), PACKAGE_GREY, SynchronousContactsProvider2.class, + ContactsContract.AUTHORITY); + mProvider = ((SynchronousContactsProvider2) mActor.provider); + mPhotoStore = mProvider.getPhotoStore(); + mProvider.wipeData(); + mDb = mProvider.getDatabaseHelper(getContext()).getReadableDatabase(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + mPhotoStore.clear(); + } + + public void testStoreThumbnailPhoto() throws IOException { + byte[] photo = loadPhotoFromResource(R.drawable.earth_small, PhotoSize.ORIGINAL); + + // Since the photo is already thumbnail-sized, no file will be stored. + assertEquals(0, mPhotoStore.insert(new PhotoProcessor(photo, 256, 96))); + } + + public void testStoreMediumPhoto() throws IOException { + runStorageTestForResource(R.drawable.earth_normal); + } + + public void testStoreLargePhoto() throws IOException { + runStorageTestForResource(R.drawable.earth_large); + } + + public void testStoreHugePhoto() throws IOException { + runStorageTestForResource(R.drawable.earth_huge); + } + + /** + * Runs the following steps: + * - Loads the given photo resource. + * - Inserts it into the photo store. + * - Checks that the photo has a photo file ID. + * - Loads the expected display photo for the resource. + * - Gets the photo entry from the photo store. + * - Loads the photo entry's file content from disk. + * - Compares the expected photo content to the disk content. + * - Queries the contacts provider for the photo file entry, checks for its + * existence, and matches it up against the expected metadata. + * - Checks that the total storage taken up by the photo store is equal to + * the size of the photo. + * @param resourceId The resource ID of the photo file to test. + */ + public void runStorageTestForResource(int resourceId) throws IOException { + byte[] photo = loadPhotoFromResource(resourceId, PhotoSize.ORIGINAL); + long photoFileId = mPhotoStore.insert(new PhotoProcessor(photo, 256, 96)); + assertTrue(photoFileId != 0); + + byte[] expectedStoredVersion = loadPhotoFromResource(resourceId, PhotoSize.DISPLAY_PHOTO); + File storedFile = new File(mPhotoStore.get(photoFileId).path); + assertTrue(storedFile.exists()); + byte[] storedVersion = readInputStreamFully(new FileInputStream(storedFile)); + assertEquals(Hex.encodeHex(expectedStoredVersion, false), + Hex.encodeHex(storedVersion, false)); + + Cursor c = mDb.query(Tables.PHOTO_FILES, + new String[]{PhotoFiles.HEIGHT, PhotoFiles.WIDTH, PhotoFiles.FILESIZE}, + PhotoFiles._ID + "=?", new String[]{String.valueOf(photoFileId)}, null, null, null); + try { + assertEquals(1, c.getCount()); + c.moveToFirst(); + assertEquals(256, c.getInt(0)); + assertEquals(256, c.getInt(1)); + assertEquals(expectedStoredVersion.length, c.getInt(2)); + } finally { + c.close(); + } + + assertEquals(expectedStoredVersion.length, mPhotoStore.getTotalSize()); + } + + public void testRemoveEntry() throws IOException { + byte[] photo = loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.ORIGINAL); + long photoFileId = mPhotoStore.insert(new PhotoProcessor(photo, 256, 96)); + PhotoStore.Entry entry = mPhotoStore.get(photoFileId); + assertTrue(new File(entry.path).exists()); + + mPhotoStore.remove(photoFileId); + + // Check that the file has been deleted. + assertFalse(new File(entry.path).exists()); + + // Check that the database record has also been removed. + Cursor c = mDb.query(Tables.PHOTO_FILES, new String[]{PhotoFiles._ID}, + PhotoFiles._ID + "=?", new String[]{String.valueOf(photoFileId)}, null, null, null); + try { + assertEquals(0, c.getCount()); + } finally { + c.close(); + } + } + + public void testCleanup() throws IOException { + // Load some photos into the store. + Set<Long> photoFileIds = new HashSet<Long>(); + Map<Integer, Long> resourceIdToPhotoMap = new HashMap<Integer, Long>(); + int[] resourceIds = new int[] { + R.drawable.earth_normal, R.drawable.earth_large, R.drawable.earth_huge + }; + for (int resourceId : resourceIds) { + long photoFileId = mPhotoStore.insert( + new PhotoProcessor(loadPhotoFromResource(resourceId, PhotoSize.ORIGINAL), + 256, 96)); + resourceIdToPhotoMap.put(resourceId, photoFileId); + photoFileIds.add(photoFileId); + } + assertFalse(photoFileIds.contains(0L)); + assertEquals(3, photoFileIds.size()); + + // Run cleanup with the indication that only the large and huge photos are in use, along + // with a bogus photo file ID that isn't in the photo store. + long bogusPhotoFileId = 42; + Set<Long> photoFileIdsInUse = new HashSet<Long>(); + photoFileIdsInUse.add(resourceIdToPhotoMap.get(R.drawable.earth_large)); + photoFileIdsInUse.add(resourceIdToPhotoMap.get(R.drawable.earth_huge)); + photoFileIdsInUse.add(bogusPhotoFileId); + + Set<Long> photoIdsToCleanup = mPhotoStore.cleanup(photoFileIdsInUse); + + // The set of photo IDs to clean up should consist of the bogus photo file ID. + assertEquals(1, photoIdsToCleanup.size()); + assertTrue(photoIdsToCleanup.contains(bogusPhotoFileId)); + + // The entry for the normal-sized photo should have been cleaned up, since it isn't being + // used. + long normalPhotoId = resourceIdToPhotoMap.get(R.drawable.earth_normal); + assertNull(mPhotoStore.get(normalPhotoId)); + + // Check that the database record has also been removed. + Cursor c = mDb.query(Tables.PHOTO_FILES, new String[]{PhotoFiles._ID}, + PhotoFiles._ID + "=?", new String[]{String.valueOf(normalPhotoId)}, + null, null, null); + try { + assertEquals(0, c.getCount()); + } finally { + c.close(); + } + } +} |