Commit 8a35dce1 authored by MozLando's avatar MozLando
Browse files

Merge #6918



6918: Issue #2754: Adds ThumbnailDiskCache for storing and restoring thumbnails r=jonalmeida a=gabrielluong




Co-authored-by: default avatarGabriel Luong <gabriel.luong@gmail.com>
parents 6355e734 81e0b076
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ dependencies {
    implementation Dependencies.androidx_core_ktx
    implementation Dependencies.kotlin_coroutines
    implementation Dependencies.kotlin_stdlib
    implementation Dependencies.thirdparty_disklrucache

    testImplementation project(':support-test')
    testImplementation project(':support-test-libstate')
+86 −0
Original line number Diff line number Diff line
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.browser.thumbnails.utils

import android.content.Context
import android.graphics.Bitmap
import com.jakewharton.disklrucache.DiskLruCache
import mozilla.components.support.base.log.logger.Logger
import java.io.File
import java.io.IOException

private const val MAXIMUM_CACHE_THUMBNAIL_DATA_BYTES: Long = 1024 * 1024 * 100 // 100 MB
private const val THUMBNAIL_DISK_CACHE_VERSION = 1
private const val WEBP_QUALITY = 90

/**
 * Caching thumbnail bitmaps on disk.
 */
class ThumbnailDiskCache {
    private val logger = Logger("ThumbnailDiskCache")
    private var thumbnailCache: DiskLruCache? = null
    private val thumbnailCacheWriteLock = Any()

    /**
     * Retrieves the thumbnail data from the disk cache for the given session ID or URL.
     *
     * @param context the application [Context].
     * @param sessionIdOrUrl the session ID or URL of the thumbnail to retrieve.
     * @return the [ByteArray] of the thumbnail or null if the snapshot of the entry does not exist.
     */
    internal fun getThumbnailData(context: Context, sessionIdOrUrl: String): ByteArray? {
        val snapshot = getThumbnailCache(context).get(sessionIdOrUrl) ?: return null

        return try {
            snapshot.getInputStream(0).use {
                it.buffered().readBytes()
            }
        } catch (e: IOException) {
            logger.info("Failed to read thumbnail bitmap from disk", e)
            null
        }
    }

    /**
     * Stores the given session ID or URL's thumbnail [Bitmap] into the disk cache.
     *
     * @param context the application [Context].
     * @param sessionIdOrUrl the session ID or URL.
     * @param bitmap the thumbnail [Bitmap] to store.
     */
    internal fun putThumbnailBitmap(context: Context, sessionIdOrUrl: String, bitmap: Bitmap) {
        try {
            synchronized(thumbnailCacheWriteLock) {
                val editor = getThumbnailCache(context)
                    .edit(sessionIdOrUrl) ?: return

                editor.newOutputStream(0).use { stream ->
                    bitmap.compress(Bitmap.CompressFormat.WEBP, WEBP_QUALITY, stream)
                }

                editor.commit()
            }
        } catch (e: IOException) {
            logger.info("Failed to save thumbnail bitmap to disk", e)
        }
    }

    private fun getThumbnailCacheDirectory(context: Context): File {
        val cacheDirectory = File(context.cacheDir, "mozac_browser_thumbnails")
        return File(cacheDirectory, "thumbnails")
    }

    @Synchronized
    private fun getThumbnailCache(context: Context): DiskLruCache {
        thumbnailCache?.let { return it }

        return DiskLruCache.open(
            getThumbnailCacheDirectory(context),
            THUMBNAIL_DISK_CACHE_VERSION,
            1,
            MAXIMUM_CACHE_THUMBNAIL_DATA_BYTES
        ).also { thumbnailCache = it }
    }
}
+47 −0
Original line number Diff line number Diff line
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.browser.thumbnails.utils

import android.graphics.Bitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import java.io.OutputStream

@RunWith(AndroidJUnit4::class)
class ThumbnailDiskCacheTest {

    @Test
    fun `Writing and reading bitmap bytes`() {
        val cache = ThumbnailDiskCache()
        val sessionIdOrUrl = "123"

        val bitmap: Bitmap = mock()
        Mockito.`when`(bitmap.compress(any(), ArgumentMatchers.anyInt(), any())).thenAnswer {
            Assert.assertEquals(
                Bitmap.CompressFormat.WEBP,
                it.arguments[0] as Bitmap.CompressFormat
            )
            Assert.assertEquals(90, it.arguments[1] as Int) // Quality

            val stream = it.arguments[2] as OutputStream
            stream.write("Hello World".toByteArray())
            true
        }

        cache.putThumbnailBitmap(testContext, sessionIdOrUrl, bitmap)

        val data = cache.getThumbnailData(testContext, sessionIdOrUrl)
        assertNotNull(data!!)
        Assert.assertEquals("Hello World", String(data))
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ permalink: /changelog/
    into a new component `support-images`, which provides helpers for handling images. `AndroidIconDecoder` and `IconDecoder`
    are renamed to `AndroidImageDecoder` and `ImageDecoder` in `support-images`.

* **browser-thumbnails**
  * Adds `ThumbnailDiskCache` for storing and restoring thumbnail bitmaps into a disk cache.

# 42.0.0

* [Commits](https://github.com/mozilla-mobile/android-components/compare/v41.0.0...42.0.0)