private static Key obtainVersionSignature(Context context) { PackageInfo pInfo = null; try { pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { // Should never happen. e.printStackTrace(); } final String versionCode; if (pInfo != null) { versionCode = String.valueOf(pInfo.versionCode); } else { versionCode = UUID.randomUUID().toString(); } return new ObjectKey(versionCode); }
@Test public void testAddsBitmapsToPoolIfMemoryCacheIsNotFullButCannotFitBitmap() { Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); when(cache.getMaxSize()).thenReturn((long) Util.getBitmapByteSize(bitmap) / 2); PreFillType size = new PreFillType.Builder(bitmap.getWidth(), bitmap.getHeight()).setConfig(bitmap.getConfig()) .build(); Map<PreFillType, Integer> allocationOrder = new HashMap<>(); allocationOrder.put(size, 1); getHandler(allocationOrder).run(); verify(cache, never()).put(any(Key.class), anyResource()); // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed //verify(pool).put(eq(bitmap)); //assertThat(addedBitmaps).containsExactly(bitmap); }
private static <X, Y> void registerMockModelLoader(Class<X> modelClass, Class<Y> dataClass, Y loadedData, Registry registry) { DataFetcher<Y> mockStreamFetcher = mock(DataFetcher.class); when(mockStreamFetcher.getDataClass()).thenReturn(dataClass); try { doAnswer(new Util.CallDataReady<>(loadedData)) .when(mockStreamFetcher) .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class)); } catch (Exception e) { throw new RuntimeException(e); } ModelLoader<X, Y> mockUrlLoader = mock(ModelLoader.class); when(mockUrlLoader.buildLoadData(isA(modelClass), anyInt(), anyInt(), isA(Options.class))) .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), mockStreamFetcher)); when(mockUrlLoader.handles(isA(modelClass))).thenReturn(true); ModelLoaderFactory<X, Y> mockUrlLoaderFactory = mock(ModelLoaderFactory.class); when(mockUrlLoaderFactory.build(isA(MultiModelLoaderFactory.class))) .thenReturn(mockUrlLoader); registry.replace(modelClass, dataClass, mockUrlLoaderFactory); }
@Override public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) { this.currentSourceKey = sourceKey; this.currentData = data; this.currentFetcher = fetcher; this.currentDataSource = dataSource; this.currentAttemptingKey = attemptedKey; if (Thread.currentThread() != currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this); } else { TraceCompat.beginSection("DecodeJob.decodeFromRetrievedData"); try { decodeFromRetrievedData(); } finally { TraceCompat.endSection(); } } }
@Override public File get(Key key) { String safeKey = safeKeyGenerator.getSafeKey(key); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Get: Obtained: " + safeKey + " for for Key: " + key); } File result = null; try { // It is possible that the there will be a put in between these two gets. If so that shouldn't // be a problem because we will always put the same value at the same key so our input streams // will still represent the same data. final DiskLruCache.Value value = getDiskCache().get(safeKey); if (value != null) { result = value.getFile(0); } } catch (IOException e) { if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Unable to get from disk cache", e); } } return result; }
void release(Key key) { WriteLock writeLock; synchronized (this) { writeLock = Preconditions.checkNotNull(locks.get(key)); if (writeLock.interestedThreads < 1) { throw new IllegalStateException("Cannot release a lock that is not held" + ", key: " + key + ", interestedThreads: " + writeLock.interestedThreads); } writeLock.interestedThreads--; if (writeLock.interestedThreads == 0) { WriteLock removed = locks.remove(key); if (!removed.equals(writeLock)) { throw new IllegalStateException("Removed the wrong lock" + ", expected to remove: " + writeLock + ", but actually removed: " + removed + ", key: " + key); } writeLockPool.offer(removed); } } writeLock.lock.unlock(); }
List<Key> getCacheKeys() { if (!isCacheKeysSet) { isCacheKeysSet = true; cacheKeys.clear(); List<LoadData<?>> loadData = getLoadData(); int size = loadData.size(); for (int i = 0; i < size; i++) { LoadData<?> data = loadData.get(i); if (!cacheKeys.contains(data.sourceKey)) { cacheKeys.add(data.sourceKey); } for (int j = 0; j < data.alternateKeys.size(); j++) { if (!cacheKeys.contains(data.alternateKeys.get(j))) { cacheKeys.add(data.alternateKeys.get(j)); } } } } return cacheKeys; }
@Override public LoadData<Data> buildLoadData(Model model, int width, int height, Options options) { Key sourceKey = null; int size = modelLoaders.size(); List<DataFetcher<Data>> fetchers = new ArrayList<>(size); for (int i = 0; i < size; i++) { ModelLoader<Model, Data> modelLoader = modelLoaders.get(i); if (modelLoader.handles(model)) { LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options); if (loadData != null) { sourceKey = loadData.sourceKey; fetchers.add(loadData.fetcher); } } } return !fetchers.isEmpty() ? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool)) : null; }
@SuppressWarnings("unchecked") private <T, Z> void registerFailFactory(Class<T> failModel, Class<Z> failResource) throws Exception { DataFetcher<Z> failFetcher = mock(DataFetcher.class); doAnswer(new Util.CallDataReady<>(null)) .when(failFetcher) .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class)); when(failFetcher.getDataClass()).thenReturn(failResource); ModelLoader<T, Z> failLoader = mock(ModelLoader.class); when(failLoader.buildLoadData(isA(failModel), anyInt(), anyInt(), isA(Options.class))) .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), failFetcher)); when(failLoader.handles(isA(failModel))).thenReturn(true); ModelLoaderFactory<T, Z> failFactory = mock(ModelLoaderFactory.class); when(failFactory.build(isA(MultiModelLoaderFactory.class))).thenReturn(failLoader); Glide.get(getContext()).getRegistry().prepend(failModel, failResource, failFactory); }
@Test public void testAddsBitmapsToBitmapPoolIfMemoryCacheIsFull() { Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); when(cache.getMaxSize()).thenReturn(0); PreFillType size = new PreFillType.Builder(bitmap.getWidth(), bitmap.getHeight()).setConfig(bitmap.getConfig()) .build(); Map<PreFillType, Integer> allocationOrder = new HashMap<>(); allocationOrder.put(size, 1); getHandler(allocationOrder).run(); verify(cache, never()).put(any(Key.class), anyResource()); // TODO(b/20335397): This code was relying on Bitmap equality which Robolectric removed // verify(pool).put(eq(bitmap)); // assertThat(addedBitmaps).containsExactly(bitmap); }
@Test public void testBuildsNewUrlIfNotPresentInCache() { int width = 10; int height = 11; urlLoader.resultUrl = "fakeUrl"; when(wrapped.buildLoadData(any(GlideUrl.class), eq(width), eq(height), eq(options))) .thenAnswer(new Answer<ModelLoader.LoadData<InputStream>>() { @Override public ModelLoader.LoadData<InputStream> answer(InvocationOnMock invocationOnMock) throws Throwable { GlideUrl glideUrl = (GlideUrl) invocationOnMock.getArguments()[0]; assertEquals(urlLoader.resultUrl, glideUrl.toStringUrl()); return new ModelLoader.LoadData<>(mock(Key.class), fetcher); } }); assertEquals(fetcher, urlLoader.buildLoadData(new GlideUrl(urlLoader.resultUrl), width, height, options).fetcher); }
@SuppressWarnings("unchecked") private <T, Z> void registerFailFactory(Class<T> failModel, Class<Z> failResource) { DataFetcher<Z> failFetcher = mock(DataFetcher.class); doAnswer(new Util.CallDataReady<>(null)) .when(failFetcher) .loadData(isA(Priority.class), isA(DataFetcher.DataCallback.class)); when(failFetcher.getDataClass()).thenReturn(failResource); ModelLoader<T, Z> failLoader = mock(ModelLoader.class); when(failLoader.buildLoadData(isA(failModel), anyInt(), anyInt(), isA(Options.class))) .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), failFetcher)); when(failLoader.handles(isA(failModel))).thenReturn(true); ModelLoaderFactory<T, Z> failFactory = mock(ModelLoaderFactory.class); when(failFactory.build(isA(MultiModelLoaderFactory.class))).thenReturn(failLoader); Glide.get(context).getRegistry().prepend(failModel, failResource, failFactory); }
/** * Returns the signature {@link com.bumptech.glide.load.Key} for version code of the Application * of the given Context. */ public static Key obtain(Context context) { String packageName = context.getPackageName(); Key result = PACKAGE_NAME_TO_KEY.get(packageName); if (result == null) { Key toAdd = obtainVersionSignature(context); result = PACKAGE_NAME_TO_KEY.putIfAbsent(packageName, toAdd); // There wasn't a previous mapping, so toAdd is now the Key. if (result == null) { result = toAdd; } } return result; }
@Before public void setUp() { MockitoAnnotations.initMocks(this); doAnswer(new AddBitmapPoolAnswer(addedBitmaps)).when(pool).put(any(Bitmap.class)); when(pool.getDirty(anyInt(), anyInt(), any(Bitmap.Config.class))) .thenAnswer(new CreateBitmap()); when(cache.put(any(Key.class), anyResource())) .thenAnswer(new AddBitmapCacheAnswer(addedBitmaps)); }
private KeyTester addRegressionTestInternal(Key key, String expectedDigest) { isUsedWithoutCallingTest = true; String oldValue = regressionTests.put(key, expectedDigest); if (oldValue != null) { throw new IllegalArgumentException( "Given multiple values for: " + key + " old: " + oldValue + " new: " + expectedDigest); } return this; }
/** * @see GlideOptions#signature(Key) */ @CheckResult public GlideRequest<TranscodeType> signature(@NonNull Key arg0) { if (getMutableOptions() instanceof GlideOptions) { this.requestOptions = ((GlideOptions) getMutableOptions()).signature(arg0); } else { this.requestOptions = new GlideOptions().apply(this.requestOptions).signature(arg0); } return this; }
@Override public void delete(Key key) { String safeKey = safeKeyGenerator.getSafeKey(key); try { getDiskCache().remove(safeKey); } catch (IOException e) { if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Unable to delete from disk cache", e); } } }
@Test public void testPreventEviction() { final MemoryCache cache = new LruResourceCache(100); final Resource<?> first = getResource(30); final Key firstKey = new MockKey(); cache.put(firstKey, first); Resource<?> second = getResource(30); Key secondKey = new MockKey(); cache.put(secondKey, second); Resource<?> third = getResource(30); Key thirdKey = new MockKey(); cache.put(thirdKey, third); cache.setResourceRemovedListener(new ResourceRemovedListener() { @Override public void onResourceRemoved(Resource<?> removed) { if (removed == first) { cache.put(firstKey, first); } } }); // trims from 100 to 50, having 30+30+30 items, it should trim to 1 item cache.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); // and that 1 item must be first, because it's forced to return to cache in the listener @SuppressWarnings("unchecked") LruCache<Key, Resource<?>> lruCache = (LruCache<Key, Resource<?>>) cache; assertTrue(lruCache.contains(firstKey)); assertFalse(lruCache.contains(secondKey)); assertFalse(lruCache.contains(thirdKey)); }
@Override public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) { this.currentSourceKey = sourceKey; this.currentData = data; this.currentFetcher = fetcher; this.currentDataSource = dataSource; this.currentAttemptingKey = attemptedKey; if (Thread.currentThread() != currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this); } else { decodeFromRetrievedData(); } }
@Override public void put(Key key, Writer writer) { // We want to make sure that puts block so that data is available when put completes. We may // actually not write any data if we find that data is written by the time we acquire the lock. String safeKey = safeKeyGenerator.getSafeKey(key); writeLocker.acquire(safeKey); try { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Put: Obtained: " + safeKey + " for for Key: " + key); } try { // We assume we only need to put once, so if data was written while we were trying to get // the lock, we can simply abort. DiskLruCache diskCache = getDiskCache(); Value current = diskCache.get(safeKey); if (current != null) { return; } DiskLruCache.Editor editor = diskCache.edit(safeKey); if (editor == null) { throw new IllegalStateException("Had two simultaneous puts for: " + safeKey); } try { File file = editor.getFile(0); if (writer.write(file)) { editor.commit(); } } finally { editor.abortUnlessCommitted(); } } catch (IOException e) { if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Unable to put to disk cache", e); } } } finally { writeLocker.release(safeKey); } }
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) { cached.acquire(); activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue())); } return cached; }
@SuppressWarnings("unchecked") private EngineResource<?> getEngineResourceFromCache(Key key) { Resource<?> cached = cache.remove(key); final EngineResource<?> result; if (cached == null) { result = null; } else if (cached instanceof EngineResource) { // Save an object allocation if we've cached an EngineResource (the typical case). result = (EngineResource<?>) cached; } else { result = new EngineResource<>(cached, true /*isMemoryCacheable*/); } return result; }
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) { cached.acquire(); activeResources.activate(key, cached); } return cached; }
@SuppressWarnings("rawtypes") public EngineKey buildKey(Object model, Key signature, int width, int height, Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass, Class<?> transcodeClass, Options options) { return new EngineKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); }
@Override public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) { // This data fetcher will be loading from a File and provide the wrong data source, so override // with the data source of the original fetcher cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey); }
@Test public void testEquals() { keyTester .addEquivalenceGroup( EmptySignature.obtain(), EmptySignature.obtain()) .addEquivalenceGroup(mock(Key.class)) .addEmptyDigestRegressionTest(EmptySignature.obtain()) .test(); }
@Synthetic @SuppressWarnings("WeakerAccess") ResourceWeakReference( Key key, EngineResource<?> r, ReferenceQueue<? super EngineResource<?>> q) { super(r, q); this.key = Preconditions.checkNotNull(key); this.resource = Preconditions.checkNotNull(r.getResource()); isCacheable = r.isCacheable(); }
@Override public void onBindItemViewHolder(RecentPhotoViewHolder viewHolder, @NonNull Cursor cursor) { viewHolder.imageView.setImageDrawable(null); long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)); long dateTaken = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_TAKEN)); long dateModified = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_MODIFIED)); String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE)); int orientation = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION)); final Uri uri = Uri.withAppendedPath(baseUri, Long.toString(id)); Key signature = new MediaStoreSignature(mimeType, dateModified, orientation); Glide.with(getContext()) .fromMediaStore() .load(uri) .signature(signature) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(viewHolder.imageView); viewHolder.imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (clickedListener != null) clickedListener.onItemClicked(uri); } }); }
private static List<Key> getAlternateKeys(List<String> alternateUrls) { List<Key> result = new ArrayList<>(alternateUrls.size()); for (String alternate : alternateUrls) { result.add(new GlideUrl(alternate)); } return result; }
private byte[] getDigest(Key key) { try { key.updateDiskCacheKey(digest); return digest.digest(); } finally { digest.reset(); } }
@Test public void testReturnsUrlFromCacheIfPresent() { Object model = new Object(); int width = 100; int height = 200; GlideUrl expectedUrl = mock(GlideUrl.class); when(modelCache.get(eq(model), eq(width), eq(height))).thenReturn(expectedUrl); when(wrapped.buildLoadData(eq(expectedUrl), eq(width), eq(height), eq(options))) .thenReturn(new ModelLoader.LoadData<>(mock(Key.class), fetcher)); assertEquals( fetcher, Preconditions.checkNotNull(urlLoader.buildLoadData(model, width, height, options)).fetcher); }
@Override public void onBindViewHolder(ListViewHolder viewHolder, int position) { MediaStoreData current = data.get(position); Key signature = new MediaStoreSignature(current.mimeType, current.dateModified, current.orientation); requestBuilder .clone() .signature(signature) .load(current.uri) .into(viewHolder.image); }