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