1 /* 2 * Copyright (C) 2018 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 17 package android.util.imagepool; 18 19 import org.junit.Ignore; 20 import org.junit.Test; 21 22 import android.util.imagepool.ImagePool.Image; 23 import android.util.imagepool.ImagePool.ImagePoolPolicy; 24 25 import java.awt.image.BufferedImage; 26 import java.lang.ref.SoftReference; 27 import java.util.concurrent.CountDownLatch; 28 import java.util.concurrent.TimeUnit; 29 30 import static org.junit.Assert.assertEquals; 31 import static org.junit.Assert.assertNotEquals; 32 import static org.junit.Assert.assertNotNull; 33 import static org.junit.Assert.assertTrue; 34 35 public class ImagePoolImplTest { 36 37 private static final long TIMEOUT_SEC = 3; 38 39 @Test testImagePoolInstance()40 public void testImagePoolInstance() { 41 ImagePool pool1 = ImagePoolProvider.get(); 42 ImagePool pool2 = ImagePoolProvider.get(); 43 assertNotNull(pool1); 44 assertNotNull(pool2); 45 assertEquals(pool1, pool2); 46 } 47 48 49 @Test testImageDispose()50 public void testImageDispose() throws InterruptedException { 51 int width = 700; 52 int height = 800; 53 int type = BufferedImage.TYPE_INT_ARGB; 54 CountDownLatch countDownLatch = new CountDownLatch(1); 55 ImagePoolImpl pool = getSimpleSingleBucketPool(width, height); 56 Image img1 = pool.acquire(width, height, type, 57 bufferedImage -> countDownLatch.countDown()); 58 BufferedImage img = getImg(img1); 59 assertNotNull(img); 60 img1 = null; 61 62 // ensure dispose actually loses buffered image link so it can be gc'd 63 gc(); 64 assertTrue(countDownLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS)); 65 } 66 @Test testImageDisposeFromFunction()67 public void testImageDisposeFromFunction() throws InterruptedException { 68 int width = 700; 69 int height = 800; 70 int type = BufferedImage.TYPE_INT_ARGB; 71 CountDownLatch cd = new CountDownLatch(1); 72 ImagePoolImpl pool = getSimpleSingleBucketPool(width, height); 73 74 BufferedImage img = createImageAndReturnBufferedImage(pool, width, height, type, cd); 75 assertNotNull(img); 76 77 // ensure dispose actually loses buffered image link so it can be gc'd 78 gc(); 79 assertTrue(cd.await(TIMEOUT_SEC, TimeUnit.SECONDS)); 80 } 81 82 @Test testImageDisposedAndRecycled()83 public void testImageDisposedAndRecycled() throws InterruptedException { 84 int width = 700; 85 int height = 800; 86 int bucketWidth = 800; 87 int bucketHeight = 800; 88 int variant = 1; 89 int type = BufferedImage.TYPE_INT_ARGB; 90 ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy( 91 new int[]{bucketWidth, bucketHeight}, 92 new int[]{1, 1}, 93 bucketHeight * bucketWidth * 4 * 3)); 94 95 // acquire first image and draw something. 96 BufferedImage bufferedImageForImg1; 97 final CountDownLatch countDownLatch1 = new CountDownLatch(1); 98 { 99 Image img1 = pool.acquire(width, height, type, 100 bufferedImage -> countDownLatch1.countDown()); 101 bufferedImageForImg1 = getImg(img1); 102 img1 = null; // this is still needed. 103 } 104 // dispose 105 gc(); 106 assertTrue(countDownLatch1.await(TIMEOUT_SEC, TimeUnit.SECONDS)); 107 108 // ensure dispose actually loses buffered image link so it can be gc'd 109 assertNotNull(bufferedImageForImg1); 110 assertEquals(bufferedImageForImg1.getWidth(), bucketWidth); 111 assertEquals(bufferedImageForImg1.getHeight(), bucketHeight); 112 113 // get 2nd image with the same spec 114 final CountDownLatch countDownLatch2 = new CountDownLatch(1); 115 BufferedImage bufferedImageForImg2; 116 { 117 Image img2 = pool.acquire(width - variant, height - variant, type, 118 bufferedImage -> countDownLatch2.countDown()); 119 bufferedImageForImg2 = getImg(img2); 120 assertEquals(bufferedImageForImg1, bufferedImageForImg2); 121 img2 = null; 122 } 123 // dispose 124 gc(); 125 assertTrue(countDownLatch2.await(TIMEOUT_SEC, TimeUnit.SECONDS)); 126 127 // ensure that we're recycling previously created buffered image. 128 assertNotNull(bufferedImageForImg1); 129 assertNotNull(bufferedImageForImg2); 130 } 131 132 133 @Test testBufferedImageReleased()134 public void testBufferedImageReleased() throws InterruptedException { 135 int width = 700; 136 int height = 800; 137 int bucketWidth = 800; 138 int bucketHeight = 800; 139 ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy( 140 new int[]{bucketWidth, bucketHeight}, 141 new int[]{1, 1}, 142 bucketWidth * bucketWidth * 4 * 2)); 143 CountDownLatch countDownLatch = new CountDownLatch(1); 144 Image image1 = pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB, 145 bufferedImage -> countDownLatch.countDown()); 146 BufferedImage internalPtr = getImg(image1); 147 // dispose 148 image1 = null; 149 gc(); 150 assertTrue(countDownLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS)); 151 152 // Simulate BufferedBitmaps being gc'd. Bucket filled with null soft refs. 153 for (Bucket bucket : ((ImagePoolImpl) pool).mPool.values()) { 154 bucket.mBufferedImageRef.clear(); 155 bucket.mBufferedImageRef.add(new SoftReference<>(null)); 156 bucket.mBufferedImageRef.add(new SoftReference<>(null)); 157 } 158 159 assertNotEquals(internalPtr, 160 getImg(pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB))); 161 } 162 163 @Test testPoolWidthHeightNotBigEnough()164 public void testPoolWidthHeightNotBigEnough() { 165 int width = 1000; 166 int height = 1000; 167 int bucketWidth = 999; 168 int bucketHeight = 800; 169 ImagePool pool = new ImagePoolImpl( 170 new ImagePoolPolicy(new int[]{bucketWidth, bucketHeight}, new int[]{1, 1}, 171 bucketWidth * bucketWidth * 4 * 2)); 172 ImageImpl image = (ImageImpl) pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB); 173 174 assertEquals(getTooBigForPoolCount(pool), 1); 175 } 176 177 @Test testSizeNotBigEnough()178 public void testSizeNotBigEnough() { 179 int width = 500; 180 int height = 500; 181 int bucketWidth = 800; 182 int bucketHeight = 800; 183 ImagePoolImpl pool = new ImagePoolImpl( 184 new ImagePoolPolicy(new int[]{bucketWidth, bucketHeight}, new int[]{1, 1}, 185 bucketWidth * bucketWidth)); // cache not big enough. 186 ImageImpl image = (ImageImpl) pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB); 187 188 assertEquals(getTooBigForPoolCount(pool), 1); 189 assertEquals(image.getWidth(), width); 190 assertEquals(image.getHeight(), height); 191 } 192 193 @Test testImageMultipleCopies()194 public void testImageMultipleCopies() throws InterruptedException { 195 int width = 700; 196 int height = 800; 197 int bucketWidth = 800; 198 int bucketHeight = 800; 199 int type = BufferedImage.TYPE_INT_ARGB; 200 ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy( 201 new int[]{bucketWidth, bucketHeight}, 202 new int[]{2, 2}, 203 bucketHeight * bucketWidth * 4 * 4)); 204 205 // create 1, and 2 different instances. 206 final CountDownLatch cd1 = new CountDownLatch(1); 207 Image img1 = pool.acquire(width, height, type, bufferedImage -> cd1.countDown()); 208 BufferedImage bufferedImg1 = getImg(img1); 209 210 Image img2 = pool.acquire(width, height, type); 211 BufferedImage bufferedImg2 = getImg(img2); 212 213 assertNotEquals(bufferedImg1, bufferedImg2); 214 215 // disposing img1. Since # of copies == 2, this buffer should be recycled. 216 img1 = null; 217 gc(); 218 cd1.await(TIMEOUT_SEC, TimeUnit.SECONDS); 219 220 // Ensure bufferedImg1 is recycled in newly acquired img3. 221 Image img3 = pool.acquire(width, height, type); 222 BufferedImage bufferedImage3 = getImg(img3); 223 assertNotEquals(bufferedImg2, bufferedImage3); 224 assertEquals(bufferedImg1, bufferedImage3); 225 } 226 227 @Ignore("b/132614809") 228 @Test testPoolDispose()229 public void testPoolDispose() throws InterruptedException { 230 int width = 700; 231 int height = 800; 232 int bucketWidth = 800; 233 int bucketHeight = 800; 234 int type = BufferedImage.TYPE_INT_ARGB; 235 236 // Pool barely enough for 1 image. 237 ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy( 238 new int[]{bucketWidth, bucketHeight}, 239 new int[]{2, 2}, 240 bucketHeight * bucketWidth * 4)); 241 242 // create 1, and 2 different instances. 243 final CountDownLatch cd1 = new CountDownLatch(1); 244 Image img1 = pool.acquire(width, height, type, bufferedImage -> cd1.countDown()); 245 BufferedImage bufferedImg1 = getImg(img1); 246 assertEquals(getAllocatedTotalBytes(pool), bucketWidth * bucketHeight * 4); 247 assertEquals(getTooBigForPoolCount(pool), 0); 248 249 // Release the img1. 250 img1 = null; 251 gc(); 252 cd1.await(TIMEOUT_SEC, TimeUnit.SECONDS); 253 254 // Dispose pool. 255 pool.dispose(); 256 assertEquals(getAllocatedTotalBytes(pool), 0); 257 258 // Request the same sized image as previous. 259 // If the pool was not disposed, this would return the image with bufferedImg1. 260 Image img2 = pool.acquire(width, height, type); 261 BufferedImage bufferedImg2 = getImg(img2); 262 assertEquals(getAllocatedTotalBytes(pool), bucketWidth * bucketHeight * 4); 263 assertEquals(getTooBigForPoolCount(pool), 0); 264 265 // Pool disposed before. No buffered image should be recycled. 266 assertNotEquals(img1, img2); 267 assertNotEquals(bufferedImg1, bufferedImg2); 268 } 269 createImageAndReturnBufferedImage(ImagePoolImpl pool, int width, int height , int type, CountDownLatch cd)270 private static BufferedImage createImageAndReturnBufferedImage(ImagePoolImpl pool, int width, 271 int height 272 , int type, CountDownLatch cd) { 273 Image img1 = pool.acquire(width, height, type, bufferedImage -> cd.countDown()); 274 return getImg(img1); 275 // At this point img1 should have no reference, causing finalizable to trigger 276 } 277 getSimpleSingleBucketPool(int width, int height)278 private static ImagePoolImpl getSimpleSingleBucketPool(int width, int height) { 279 280 int bucketWidth = Math.max(width, height); 281 int bucketHeight = Math.max(width, height); 282 return new ImagePoolImpl(new ImagePoolPolicy( 283 new int[]{bucketWidth, bucketHeight}, 284 new int[]{1, 1}, 285 bucketHeight * bucketWidth * 4 * 3)); 286 } 287 288 // Try to force a gc round gc()289 private static void gc() { 290 System.gc(); 291 System.gc(); 292 System.gc(); 293 } 294 getTooBigForPoolCount(ImagePool pool)295 private static int getTooBigForPoolCount(ImagePool pool) { 296 return ((ImagePoolStatsProdImpl) ((ImagePoolImpl) pool).mImagePoolStats).mTooBigForPoolCount; 297 } 298 getAllocatedTotalBytes(ImagePool pool)299 private static long getAllocatedTotalBytes(ImagePool pool) { 300 return ((ImagePoolStatsProdImpl) ((ImagePoolImpl) pool).mImagePoolStats).mAllocateTotalBytes; 301 } 302 getImg(Image image)303 private static BufferedImage getImg(Image image) { 304 return ((ImageImpl) image).mImg; 305 } 306 } 307