1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.providers.contacts; 18 19import com.android.providers.contacts.ContactsDatabaseHelper.Tables; 20import com.android.providers.contacts.tests.R; 21import com.android.providers.contacts.util.Hex; 22 23import android.database.Cursor; 24import android.database.sqlite.SQLiteDatabase; 25import android.provider.ContactsContract; 26import android.provider.ContactsContract.PhotoFiles; 27import android.test.suitebuilder.annotation.MediumTest; 28 29import java.io.File; 30import java.io.FileInputStream; 31import java.io.IOException; 32import java.util.Arrays; 33import java.util.HashMap; 34import java.util.HashSet; 35import java.util.Map; 36import java.util.Set; 37 38import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY; 39 40/** 41 * Tests for {@link PhotoStore}. 42 */ 43@MediumTest 44public class PhotoStoreTest extends PhotoLoadingTestCase { 45 46 private ContactsActor mActor; 47 private SynchronousContactsProvider2 mProvider; 48 private SQLiteDatabase mDb; 49 50 // The object under test. 51 private PhotoStore mPhotoStore; 52 53 @Override 54 protected void setUp() throws Exception { 55 super.setUp(); 56 mActor = new ContactsActor(getContext(), PACKAGE_GREY, SynchronousContactsProvider2.class, 57 ContactsContract.AUTHORITY); 58 mProvider = ((SynchronousContactsProvider2) mActor.provider); 59 mPhotoStore = mProvider.getPhotoStore(); 60 mProvider.wipeData(); 61 mDb = mProvider.getDatabaseHelper(getContext()).getReadableDatabase(); 62 } 63 64 @Override 65 protected void tearDown() throws Exception { 66 super.tearDown(); 67 mPhotoStore.clear(); 68 } 69 70 public void testStoreThumbnailPhoto() throws IOException { 71 byte[] photo = loadPhotoFromResource(R.drawable.earth_small, PhotoSize.ORIGINAL); 72 73 // Since the photo is already thumbnail-sized, no file will be stored. 74 assertEquals(0, mPhotoStore.insert(newPhotoProcessor(photo, false))); 75 } 76 77 public void testStore200Photo() throws IOException { 78 // As 200 is below the full photo size, we don't want to see it upscaled 79 runStorageTestForResource(R.drawable.earth_200, 200, 200); 80 } 81 82 public void testStoreNonSquare300x200Photo() throws IOException { 83 // The longer side should be downscaled to the target size 84 runStorageTestForResource(R.drawable.earth_300x200, 256, 171); 85 } 86 87 public void testStoreNonSquare300x200PhotoWithCrop() throws IOException { 88 // As 300x200 is below the full photo size, we don't want to see it upscaled 89 // This one is not square, so we expect the longer side to be cropped 90 runStorageTestForResourceWithCrop(R.drawable.earth_300x200, 200, 200); 91 } 92 93 public void testStoreNonSquare600x400PhotoWithCrop() throws IOException { 94 // As 600x400 is above the full photo size, we expect the picture to be cropped and then 95 // scaled 96 runStorageTestForResourceWithCrop(R.drawable.earth_600x400, 256, 256); 97 } 98 99 public void testStoreMediumPhoto() throws IOException { 100 // Source Image is 256x256 101 runStorageTestForResource(R.drawable.earth_normal, 256, 256); 102 } 103 104 public void testStoreLargePhoto() throws IOException { 105 // Source image is 512x512 106 runStorageTestForResource(R.drawable.earth_large, 256, 256); 107 } 108 109 public void testStoreHugePhoto() throws IOException { 110 // Source image is 1024x1024 111 runStorageTestForResource(R.drawable.earth_huge, 256, 256); 112 } 113 114 /** 115 * Runs the following steps: 116 * - Loads the given photo resource. 117 * - Inserts it into the photo store. 118 * - Checks that the photo has a photo file ID. 119 * - Loads the expected display photo for the resource. 120 * - Gets the photo entry from the photo store. 121 * - Loads the photo entry's file content from disk. 122 * - Compares the expected photo content to the disk content. 123 * - Queries the contacts provider for the photo file entry, checks for its 124 * existence, and matches it up against the expected metadata. 125 * - Checks that the total storage taken up by the photo store is equal to 126 * the size of the photo. 127 * @param resourceId The resource ID of the photo file to test. 128 */ 129 public void runStorageTestForResource(int resourceId, int expectedWidth, 130 int expectedHeight) throws IOException { 131 byte[] photo = loadPhotoFromResource(resourceId, PhotoSize.ORIGINAL); 132 long photoFileId = mPhotoStore.insert(newPhotoProcessor(photo, false)); 133 assertTrue(photoFileId != 0); 134 135 File storedFile = new File(mPhotoStore.get(photoFileId).path); 136 assertTrue(storedFile.exists()); 137 byte[] actualStoredVersion = readInputStreamFully(new FileInputStream(storedFile)); 138 139 byte[] expectedStoredVersion = loadPhotoFromResource(resourceId, PhotoSize.DISPLAY_PHOTO); 140 141 EvenMoreAsserts.assertImageRawData(getContext(), 142 expectedStoredVersion, actualStoredVersion); 143 144 Cursor c = mDb.query(Tables.PHOTO_FILES, 145 new String[]{PhotoFiles.WIDTH, PhotoFiles.HEIGHT, PhotoFiles.FILESIZE}, 146 PhotoFiles._ID + "=?", new String[]{String.valueOf(photoFileId)}, null, null, null); 147 try { 148 assertEquals(1, c.getCount()); 149 c.moveToFirst(); 150 assertEquals(expectedWidth + "/" + expectedHeight, c.getInt(0) + "/" + c.getInt(1)); 151 assertEquals(expectedStoredVersion.length, c.getInt(2)); 152 } finally { 153 c.close(); 154 } 155 156 assertEquals(expectedStoredVersion.length, mPhotoStore.getTotalSize()); 157 } 158 159 public void runStorageTestForResourceWithCrop(int resourceId, int expectedWidth, 160 int expectedHeight) throws IOException { 161 byte[] photo = loadPhotoFromResource(resourceId, PhotoSize.ORIGINAL); 162 long photoFileId = mPhotoStore.insert(newPhotoProcessor(photo, true)); 163 assertTrue(photoFileId != 0); 164 165 Cursor c = mDb.query(Tables.PHOTO_FILES, 166 new String[]{PhotoFiles.HEIGHT, PhotoFiles.WIDTH, PhotoFiles.FILESIZE}, 167 PhotoFiles._ID + "=?", new String[]{String.valueOf(photoFileId)}, null, null, null); 168 try { 169 assertEquals(1, c.getCount()); 170 c.moveToFirst(); 171 assertEquals(expectedWidth + "/" + expectedHeight, c.getInt(0) + "/" + c.getInt(1)); 172 } finally { 173 c.close(); 174 } 175 } 176 177 public void testRemoveEntry() throws IOException { 178 byte[] photo = loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.ORIGINAL); 179 long photoFileId = mPhotoStore.insert(newPhotoProcessor(photo, false)); 180 PhotoStore.Entry entry = mPhotoStore.get(photoFileId); 181 assertTrue(new File(entry.path).exists()); 182 183 mPhotoStore.remove(photoFileId); 184 185 // Check that the file has been deleted. 186 assertFalse(new File(entry.path).exists()); 187 188 // Check that the database record has also been removed. 189 Cursor c = mDb.query(Tables.PHOTO_FILES, new String[]{PhotoFiles._ID}, 190 PhotoFiles._ID + "=?", new String[]{String.valueOf(photoFileId)}, null, null, null); 191 try { 192 assertEquals(0, c.getCount()); 193 } finally { 194 c.close(); 195 } 196 } 197 198 public void testCleanup() throws IOException { 199 // Load some photos into the store. 200 Set<Long> photoFileIds = new HashSet<Long>(); 201 Map<Integer, Long> resourceIdToPhotoMap = new HashMap<Integer, Long>(); 202 int[] resourceIds = new int[] { 203 R.drawable.earth_normal, R.drawable.earth_large, R.drawable.earth_huge 204 }; 205 for (int resourceId : resourceIds) { 206 long photoFileId = mPhotoStore.insert( 207 new PhotoProcessor(loadPhotoFromResource(resourceId, PhotoSize.ORIGINAL), 208 256, 96)); 209 resourceIdToPhotoMap.put(resourceId, photoFileId); 210 photoFileIds.add(photoFileId); 211 } 212 assertFalse(photoFileIds.contains(0L)); 213 assertEquals(3, photoFileIds.size()); 214 215 // Run cleanup with the indication that only the large and huge photos are in use, along 216 // with a bogus photo file ID that isn't in the photo store. 217 long bogusPhotoFileId = 123456789; 218 Set<Long> photoFileIdsInUse = new HashSet<Long>(); 219 photoFileIdsInUse.add(resourceIdToPhotoMap.get(R.drawable.earth_large)); 220 photoFileIdsInUse.add(resourceIdToPhotoMap.get(R.drawable.earth_huge)); 221 photoFileIdsInUse.add(bogusPhotoFileId); 222 223 Set<Long> photoIdsToCleanup = mPhotoStore.cleanup(photoFileIdsInUse); 224 225 // The set of photo IDs to clean up should consist of the bogus photo file ID. 226 assertEquals(1, photoIdsToCleanup.size()); 227 assertTrue(photoIdsToCleanup.contains(bogusPhotoFileId)); 228 229 // The entry for the normal-sized photo should have been cleaned up, since it isn't being 230 // used. 231 long normalPhotoId = resourceIdToPhotoMap.get(R.drawable.earth_normal); 232 assertNull(mPhotoStore.get(normalPhotoId)); 233 234 // Check that the database record has also been removed. 235 Cursor c = mDb.query(Tables.PHOTO_FILES, new String[]{PhotoFiles._ID}, 236 PhotoFiles._ID + "=?", new String[]{String.valueOf(normalPhotoId)}, 237 null, null, null); 238 try { 239 assertEquals(0, c.getCount()); 240 } finally { 241 c.close(); 242 } 243 } 244} 245