Fix the last failing photo test

Also add some nice features to the asserts for complex data.
- assertImageRawData() now writes the expected and actual images to the
cache dir when failing.

- assertStoredValues() and its siblings now dump the cursor to logcat
when failing.

Bug 6280711

Change-Id: I77aff61b0494580d0b2ba24b35eb045a18cd48c8
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index c1449fc..19e81a9 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -1493,7 +1493,8 @@
                 new DataRowHandlerForGroupMembership(context, dbHelper, contactAggregator,
                         mGroupIdCache));
         handlerMap.put(Photo.CONTENT_ITEM_TYPE,
-                new DataRowHandlerForPhoto(context, dbHelper, contactAggregator, photoStore));
+                new DataRowHandlerForPhoto(context, dbHelper, contactAggregator, photoStore,
+                        getMaxDisplayPhotoDim(), getMaxThumbnailDim()));
         handlerMap.put(Note.CONTENT_ITEM_TYPE,
                 new DataRowHandlerForNote(context, dbHelper, contactAggregator));
     }
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
index 79f9267..7560ed4 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
@@ -34,6 +34,8 @@
     private static final String TAG = "DataRowHandlerForPhoto";
 
     private final PhotoStore mPhotoStore;
+    private final int mMaxDisplayPhotoDim;
+    private final int mMaxThumbnailPhotoDim;
 
     /**
      * If this is set in the ContentValues passed in, it indicates that the caller has
@@ -45,9 +47,11 @@
 
     public DataRowHandlerForPhoto(
             Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator,
-            PhotoStore photoStore) {
+            PhotoStore photoStore, int maxDisplayPhotoDim, int maxThumbnailPhotoDim) {
         super(context, dbHelper, aggregator, Photo.CONTENT_ITEM_TYPE);
         mPhotoStore = photoStore;
+        mMaxDisplayPhotoDim = maxDisplayPhotoDim;
+        mMaxThumbnailPhotoDim = maxThumbnailPhotoDim;
     }
 
     @Override
@@ -141,11 +145,9 @@
     private boolean processPhoto(ContentValues values) {
         byte[] originalPhoto = values.getAsByteArray(Photo.PHOTO);
         if (originalPhoto != null) {
-            int maxDisplayPhotoDim = PhotoProcessor.getMaxDisplayPhotoSize();
-            int maxThumbnailPhotoDim = PhotoProcessor.getMaxThumbnailSize();
             try {
                 PhotoProcessor processor = new PhotoProcessor(
-                        originalPhoto, maxDisplayPhotoDim, maxThumbnailPhotoDim);
+                        originalPhoto, mMaxDisplayPhotoDim, mMaxThumbnailPhotoDim);
                 long photoFileId = mPhotoStore.insert(processor);
                 if (photoFileId != 0) {
                     values.put(Photo.PHOTO_FILE_ID, photoFileId);
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 5827ff1..863ad4a 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -930,7 +930,7 @@
         assertStoredValues(rowUri, null, null, expectedValues);
     }
 
-    protected void assertStoredValues(Uri rowUri, ContentValues[] expectedValues) {
+    protected void assertStoredValues(Uri rowUri, ContentValues... expectedValues) {
         assertStoredValues(rowUri, null, null, expectedValues);
     }
 
@@ -941,6 +941,9 @@
             assertEquals("Record count", 1, c.getCount());
             c.moveToFirst();
             assertCursorValues(c, expectedValues);
+        } catch (Error e) {
+            TestUtils.dumpCursor(c);
+            throw e;
         } finally {
             c.close();
         }
@@ -957,6 +960,9 @@
             assertEquals("Record count", expectedValues.length, c.getCount());
             c.moveToFirst();
             assertCursorValues(c, expectedValues);
+        } catch (Error e) {
+            TestUtils.dumpCursor(c);
+            throw e;
         } finally {
             c.close();
         }
@@ -972,6 +978,9 @@
         try {
             assertEquals("Record count", expectedValues.length, c.getCount());
             assertCursorValues(c, expectedValues);
+        } catch (Error e) {
+            TestUtils.dumpCursor(c);
+            throw e;
         } finally {
             c.close();
         }
@@ -997,6 +1006,9 @@
         try {
             assertEquals("Record count", expectedValues.length, c.getCount());
             assertCursorValuesOrderly(c, expectedValues);
+        } catch (Error e) {
+            TestUtils.dumpCursor(c);
+            throw e;
         } finally {
             c.close();
         }
@@ -1045,6 +1057,9 @@
             assertEquals("Record count", 1, c.getCount());
             c.moveToFirst();
             assertCursorValues(c, values);
+        } catch (Error e) {
+            TestUtils.dumpCursor(c);
+            throw e;
         } finally {
             c.close();
         }
@@ -1062,7 +1077,7 @@
         assertTrue(message.toString(), result);
     }
 
-    protected void assertCursorValues(Cursor cursor, ContentValues[] expectedValues) {
+    protected void assertCursorValues(Cursor cursor, ContentValues... expectedValues) {
         StringBuilder message = new StringBuilder();
 
         // In case if expectedValues contains multiple identical values, remember which cursor
@@ -1085,7 +1100,7 @@
         }
     }
 
-    protected void assertCursorValuesOrderly(Cursor cursor, ContentValues[] expectedValues) {
+    private void assertCursorValuesOrderly(Cursor cursor, ContentValues... expectedValues) {
         StringBuilder message = new StringBuilder();
         cursor.moveToPosition(-1);
         for (ContentValues v : expectedValues) {
@@ -1220,10 +1235,15 @@
 
     protected void assertRowCount(int expectedCount, Uri uri, String selection, String[] args) {
         Cursor cursor = mResolver.query(uri, null, selection, args, null);
-        int count = cursor.getCount();
-        cursor.close();
 
-        assertEquals(expectedCount, count);
+        try {
+            assertEquals(expectedCount, cursor.getCount());
+        } catch (Error e) {
+            TestUtils.dumpCursor(cursor);
+            throw e;
+        } finally {
+            cursor.close();
+        }
     }
 
     /**
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index ec909b3..f6770de 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -2255,7 +2255,7 @@
     public void testLoadProfilePhoto() throws IOException {
         long rawContactId = createBasicProfileContact(new ContentValues());
         insertPhoto(rawContactId, R.drawable.earth_normal);
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.THUMBNAIL),
                 Contacts.openContactPhotoInputStream(mResolver, Profile.CONTENT_URI, false));
     }
@@ -2263,7 +2263,7 @@
     public void testLoadProfileDisplayPhoto() throws IOException {
         long rawContactId = createBasicProfileContact(new ContentValues());
         insertPhoto(rawContactId, R.drawable.earth_normal);
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
                 Contacts.openContactPhotoInputStream(mResolver, Profile.CONTENT_URI, true));
     }
@@ -3842,7 +3842,7 @@
                     R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO);
             while (c.moveToNext()) {
                 String photoUri = c.getString(1);
-                EvenMoreAsserts.assertImageRawData(
+                EvenMoreAsserts.assertImageRawData(getContext(),
                         expectedPhotoBytes, mResolver.openInputStream(Uri.parse(photoUri)));
             }
         } finally {
@@ -3903,7 +3903,7 @@
 
         // Check that the photo stored is the expected one.
         String displayPhotoUri = getStoredValue(photoUri, StreamItemPhotos.PHOTO_URI);
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.nebula, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(Uri.parse(displayPhotoUri)));
     }
@@ -3930,7 +3930,7 @@
 
         // Check that the photo stored is the expected one.
         String displayPhotoUri = getStoredValue(photoUri, StreamItemPhotos.PHOTO_URI);
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.nebula, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(Uri.parse(displayPhotoUri)));
     }
@@ -5080,9 +5080,9 @@
         assertStoredValues(photoLookupUriWithoutId, values);
 
         // Try opening as an input stream.
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 thumbnail, mResolver.openInputStream(photoLookupUriWithId));
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 thumbnail, mResolver.openInputStream(photoLookupUriWithoutId));
     }
 
@@ -5094,10 +5094,19 @@
         Uri photoUri = Uri.parse(getStoredValue(contactUri, Contacts.PHOTO_URI));
         Uri photoThumbnailUri = Uri.parse(getStoredValue(contactUri, Contacts.PHOTO_THUMBNAIL_URI));
 
-        EvenMoreAsserts.assertImageRawData(loadTestPhoto(PhotoSize.DISPLAY_PHOTO),
-                mResolver.openInputStream(photoUri));
-        EvenMoreAsserts.assertImageRawData(loadTestPhoto(PhotoSize.THUMBNAIL),
+        // Check the thumbnail.
+        EvenMoreAsserts.assertImageRawData(getContext(), loadTestPhoto(PhotoSize.THUMBNAIL),
                 mResolver.openInputStream(photoThumbnailUri));
+
+        // Then check the display photo.  Note because we only inserted a small photo, but not a
+        // display photo, this returns the thumbnail image itself, which was compressed at
+        // the thumnail compression rate, which is why we compare to
+        // loadTestPhoto(PhotoSize.THUMBNAIL) rather than loadTestPhoto(PhotoSize.DISPLAY_PHOTO)
+        // here.
+        // (In other words, loadTestPhoto(PhotoSize.DISPLAY_PHOTO) returns the same photo as
+        // loadTestPhoto(PhotoSize.THUMBNAIL), except it's compressed at a lower compression rate.)
+        EvenMoreAsserts.assertImageRawData(getContext(), loadTestPhoto(PhotoSize.THUMBNAIL),
+                mResolver.openInputStream(photoUri));
     }
 
     public void testSuperPrimaryPhoto() {
@@ -5208,7 +5217,7 @@
         Uri photoUri = Contacts.CONTENT_URI.buildUpon()
                 .appendPath(String.valueOf(contactId))
                 .appendPath(Contacts.Photo.DISPLAY_PHOTO).build();
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(photoUri));
     }
@@ -5221,7 +5230,7 @@
         Uri photoUri = Contacts.CONTENT_LOOKUP_URI.buildUpon()
                 .appendPath(lookupKey)
                 .appendPath(Contacts.Photo.DISPLAY_PHOTO).build();
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(photoUri));
     }
@@ -5235,7 +5244,7 @@
                 .appendPath(lookupKey)
                 .appendPath(String.valueOf(contactId))
                 .appendPath(Contacts.Photo.DISPLAY_PHOTO).build();
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(photoUri));
     }
@@ -5246,7 +5255,7 @@
         Uri photoUri = RawContacts.CONTENT_URI.buildUpon()
                 .appendPath(String.valueOf(rawContactId))
                 .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build();
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(photoUri));
     }
@@ -5260,7 +5269,7 @@
         String photoUri = getStoredValue(
                 ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
                 Contacts.PHOTO_URI);
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(Uri.parse(photoUri)));
     }
@@ -5312,7 +5321,7 @@
                 photoUri);
 
         // Loading the photo URI content should get the thumbnail.
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL),
                 mResolver.openInputStream(Uri.parse(photoUri)));
     }
@@ -5349,10 +5358,10 @@
         assertNotSame(photoUri, thumbnailUri);
 
         // Check the content of the display photo and thumbnail.
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(Uri.parse(photoUri)));
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.THUMBNAIL),
                 mResolver.openInputStream(Uri.parse(thumbnailUri)));
     }
@@ -5387,10 +5396,10 @@
         String hugeEarthThumbnailUri = getStoredValue(
                 ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
                 Contacts.PHOTO_THUMBNAIL_URI);
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.DISPLAY_PHOTO),
                 mResolver.openInputStream(Uri.parse(hugeEarthPhotoUri)));
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.THUMBNAIL),
                 mResolver.openInputStream(Uri.parse(hugeEarthThumbnailUri)));
 
@@ -5592,7 +5601,7 @@
         assertEquals(photoUri, thumbnailUri);
 
         // Retrieving the photo URI should get the thumbnail content.
-        EvenMoreAsserts.assertImageRawData(
+        EvenMoreAsserts.assertImageRawData(getContext(),
                 loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL),
                 mResolver.openInputStream(Uri.parse(photoUri)));
     }
diff --git a/tests/src/com/android/providers/contacts/EvenMoreAsserts.java b/tests/src/com/android/providers/contacts/EvenMoreAsserts.java
index 8d81d67..509a7fe 100644
--- a/tests/src/com/android/providers/contacts/EvenMoreAsserts.java
+++ b/tests/src/com/android/providers/contacts/EvenMoreAsserts.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.contacts;
 
+import android.content.Context;
 import android.graphics.BitmapFactory;
 
 import java.io.ByteArrayOutputStream;
@@ -59,7 +60,8 @@
         return userMsg == null ? errorMsg : errorMsg + userMsg;
     }
 
-    public static void assertImageRawData(byte[] expected, InputStream actualStream)
+    public static void assertImageRawData(Context context, byte[] expected,
+            InputStream actualStream)
             throws IOException {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
@@ -68,11 +70,12 @@
         while ((count = actualStream.read(buffer)) != -1) {
             baos.write(buffer, 0, count);
         }
+        actualStream.close();
 
-        assertImageRawData(expected, baos.toByteArray());
+        assertImageRawData(context, expected, baos.toByteArray());
     }
 
-    public static void assertImageRawData(byte[] expected, byte[] actual) {
+    public static void assertImageRawData(Context context, byte[] expected, byte[] actual) {
         String failReason = null;
         if (expected.length != actual.length) {
             failReason = "Different data lengths:" +
@@ -83,9 +86,13 @@
         if (failReason == null) {
             return;
         }
+        String expectedFile = TestUtils.dumpToCacheDir(context, "expected", ".jpg", expected);
+        String actualFile = TestUtils.dumpToCacheDir(context, "actual", ".jpg", actual);
+
         // Length or hashCode is different.  We'll fail, but put the dimensions in the message.
         Assert.fail(failReason + ", expected dimentions=" + getImageDimensions(expected) +
-                " actual dimentions=" + getImageDimensions(actual));
+                " actual dimentions=" + getImageDimensions(actual) +
+                " Data written to " + expectedFile + " and " + actualFile);
     }
 
     private static final String getImageDimensions(byte[] imageData) {
diff --git a/tests/src/com/android/providers/contacts/PhotoStoreTest.java b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
index 8b8abe8..1550d0b 100644
--- a/tests/src/com/android/providers/contacts/PhotoStoreTest.java
+++ b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
@@ -138,7 +138,8 @@
 
         byte[] expectedStoredVersion = loadPhotoFromResource(resourceId, PhotoSize.DISPLAY_PHOTO);
 
-        EvenMoreAsserts.assertImageRawData(expectedStoredVersion, actualStoredVersion);
+        EvenMoreAsserts.assertImageRawData(getContext(),
+                expectedStoredVersion, actualStoredVersion);
 
         Cursor c = mDb.query(Tables.PHOTO_FILES,
                 new String[]{PhotoFiles.WIDTH, PhotoFiles.HEIGHT, PhotoFiles.FILESIZE},
diff --git a/tests/src/com/android/providers/contacts/TestUtils.java b/tests/src/com/android/providers/contacts/TestUtils.java
index c7fb6ae..2d0930a 100644
--- a/tests/src/com/android/providers/contacts/TestUtils.java
+++ b/tests/src/com/android/providers/contacts/TestUtils.java
@@ -17,6 +17,13 @@
 package com.android.providers.contacts;
 
 import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 
 import junit.framework.Assert;
 
@@ -46,4 +53,46 @@
         }
         return ret;
     }
+
+    /**
+     * Writes the content of a cursor to the log.
+     */
+    public static final void dumpCursor(Cursor c) {
+        final String TAG = "contacts";
+
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < c.getColumnCount(); i++) {
+            if (sb.length() > 0) sb.append("|");
+            sb.append(c.getColumnName(i));
+        }
+        Log.i(TAG, sb.toString());
+
+        c.moveToPosition(-1);
+        while (c.moveToNext()) {
+            sb.setLength(0);
+            for (int i = 0; i < c.getColumnCount(); i++) {
+                if (sb.length() > 0) sb.append("|");
+
+                // TODO Handle binary data somehow.
+                sb.append(c.getString(i));
+            }
+            Log.i(TAG, sb.toString());
+        }
+    }
+
+    /**
+     * Writes an arbitrary byte array to the test apk's cache directory.
+     */
+    public static final String dumpToCacheDir(Context context, String prefix, String suffix,
+            byte[] data) {
+        try {
+            File file = File.createTempFile(prefix, suffix, context.getCacheDir());
+            FileOutputStream fos = new FileOutputStream(file);
+            fos.write(data);
+            fos.close();
+            return file.getAbsolutePath();
+        } catch (IOException e) {
+            return "[Failed to write to file: " + e.getMessage() + "]";
+        }
+    }
 }