This repository has been archived by the owner on Nov 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 473
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
7127: Issue #7021: Integrate the ThumbnailDiskCache with BrowserThumbnails r=jonalmeida a=gabrielluong Co-authored-by: Gabriel Luong <[email protected]>
- Loading branch information
Showing
18 changed files
with
475 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
...er/thumbnails/src/main/java/mozilla/components/browser/thumbnails/ThumbnailsMiddleware.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* 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 | ||
|
||
import mozilla.components.browser.state.action.BrowserAction | ||
import mozilla.components.browser.state.action.ContentAction | ||
import mozilla.components.browser.state.state.BrowserState | ||
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage | ||
import mozilla.components.lib.state.Middleware | ||
import mozilla.components.lib.state.MiddlewareStore | ||
|
||
/** | ||
* [Middleware] implementation for handling [ContentAction.UpdateThumbnailAction] and storing | ||
* the thumbnail to the disk cache. | ||
*/ | ||
class ThumbnailsMiddleware( | ||
private val thumbnailStorage: ThumbnailStorage | ||
) : Middleware<BrowserState, BrowserAction> { | ||
override fun invoke( | ||
store: MiddlewareStore<BrowserState, BrowserAction>, | ||
next: (BrowserAction) -> Unit, | ||
action: BrowserAction | ||
) { | ||
when (action) { | ||
is ContentAction.UpdateThumbnailAction -> { | ||
// Store the captured tab screenshot from the EngineView when the session's | ||
// thumbnail is updated. | ||
thumbnailStorage.saveThumbnail(action.sessionId, action.thumbnail) | ||
} | ||
} | ||
|
||
next(action) | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
...wser/thumbnails/src/main/java/mozilla/components/browser/thumbnails/ThumbnailsUseCases.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* 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 | ||
|
||
import android.graphics.Bitmap | ||
import mozilla.components.browser.state.selector.findTab | ||
import mozilla.components.browser.state.store.BrowserStore | ||
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage | ||
import mozilla.components.support.base.log.logger.Logger | ||
|
||
/** | ||
* Contains use cases related to the thumbnails feature. | ||
*/ | ||
class ThumbnailsUseCases( | ||
store: BrowserStore, | ||
thumbnailStorage: ThumbnailStorage | ||
) { | ||
/** | ||
* Load thumbnail use case. | ||
*/ | ||
class LoadThumbnailUseCase internal constructor( | ||
private val store: BrowserStore, | ||
private val thumbnailStorage: ThumbnailStorage | ||
) { | ||
private val logger = Logger("ThumbnailsUseCases") | ||
|
||
/** | ||
* Loads the thumbnail of a tab from its in-memory [ContentState] or from the disk cache | ||
* of [ThumbnailStorage]. | ||
*/ | ||
suspend operator fun invoke(sessionIdOrUrl: String): Bitmap? { | ||
val tab = store.state.findTab(sessionIdOrUrl) | ||
tab?.content?.thumbnail?.let { | ||
logger.debug( | ||
"Loaded thumbnail from memory (sessionIdOrUrl = $sessionIdOrUrl, " + | ||
"generationId = ${it.generationId})" | ||
) | ||
return@invoke it | ||
} | ||
|
||
return thumbnailStorage.loadThumbnail(sessionIdOrUrl).await() | ||
} | ||
} | ||
|
||
val loadThumbnail: LoadThumbnailUseCase by lazy { | ||
LoadThumbnailUseCase( | ||
store, | ||
thumbnailStorage | ||
) | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
...humbnails/src/main/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* 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.storage | ||
|
||
import android.content.Context | ||
import android.graphics.Bitmap | ||
import androidx.annotation.WorkerThread | ||
import kotlinx.coroutines.CoroutineDispatcher | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Deferred | ||
import kotlinx.coroutines.asCoroutineDispatcher | ||
import kotlinx.coroutines.async | ||
import mozilla.components.browser.thumbnails.R | ||
import mozilla.components.browser.thumbnails.utils.ThumbnailDiskCache | ||
import mozilla.components.support.base.log.logger.Logger | ||
import mozilla.components.support.images.DesiredSize | ||
import mozilla.components.support.images.decoder.AndroidImageDecoder | ||
import java.util.concurrent.Executors | ||
|
||
private const val MAXIMUM_SCALE_FACTOR = 2.0f | ||
|
||
// Number of worker threads we are using internally. | ||
private const val THREADS = 3 | ||
|
||
internal val sharedDiskCache = ThumbnailDiskCache() | ||
|
||
/** | ||
* Thumbnail storage layer which handles saving and loading the thumbnail from the disk cache. | ||
*/ | ||
class ThumbnailStorage( | ||
private val context: Context, | ||
jobDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(THREADS) | ||
.asCoroutineDispatcher() | ||
) { | ||
private val decoders = AndroidImageDecoder() | ||
private val logger = Logger("ThumbnailStorage") | ||
private val maximumSize = | ||
context.resources.getDimensionPixelSize(R.dimen.mozac_browser_thumbnails_maximum_size) | ||
private val scope = CoroutineScope(jobDispatcher) | ||
|
||
/** | ||
* Asynchronously loads a thumbnail [Bitmap] for the given session ID or url. | ||
*/ | ||
fun loadThumbnail(sessionIdOrUrl: String): Deferred<Bitmap?> = scope.async { | ||
loadThumbnailInternal(sessionIdOrUrl).also { loadedThumbnail -> | ||
if (loadedThumbnail != null) { | ||
logger.debug( | ||
"Loaded thumbnail from disk (sessionIdOrUrl = $sessionIdOrUrl, " + | ||
"generationId = ${loadedThumbnail.generationId})" | ||
) | ||
} else { | ||
logger.debug("No thumbnail loaded (sessionIdOrUrl = $sessionIdOrUrl)") | ||
} | ||
} | ||
} | ||
|
||
@WorkerThread | ||
private fun loadThumbnailInternal(sessionIdOrUrl: String): Bitmap? { | ||
val desiredSize = DesiredSize( | ||
targetSize = context.resources.getDimensionPixelSize(R.dimen.mozac_browser_thumbnails_size_default), | ||
maxSize = maximumSize, | ||
maxScaleFactor = MAXIMUM_SCALE_FACTOR | ||
) | ||
|
||
val data = sharedDiskCache.getThumbnailData(context, sessionIdOrUrl) | ||
|
||
if (data != null) { | ||
return decoders.decode(data, desiredSize) | ||
} | ||
|
||
return null | ||
} | ||
|
||
/** | ||
* Stores the given thumbnail [Bitmap] into the disk cache with the provided session ID or url | ||
* as its key. | ||
*/ | ||
fun saveThumbnail(sessionIdOrUrl: String, bitmap: Bitmap) { | ||
logger.debug( | ||
"Saved thumbnail to disk (sessionIdOrUrl = $sessionIdOrUrl, " + | ||
"generationId = ${bitmap.generationId})" | ||
) | ||
sharedDiskCache.putThumbnailBitmap(context, sessionIdOrUrl, bitmap) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<!-- 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/. --> | ||
<resources xmlns:tools="http://schemas.android.com/tools"> | ||
<dimen name="mozac_browser_thumbnails_size_default">102dp</dimen> | ||
|
||
<dimen name="mozac_browser_thumbnails_maximum_size" tools:ignore="PxUsage">2153px</dimen> | ||
</resources> |
37 changes: 37 additions & 0 deletions
37
...humbnails/src/test/java/mozilla/components/browser/thumbnails/ThumbnailsMiddlewareTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* 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 | ||
|
||
import android.graphics.Bitmap | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import mozilla.components.browser.state.action.ContentAction | ||
import mozilla.components.browser.state.state.BrowserState | ||
import mozilla.components.browser.state.state.createTab | ||
import mozilla.components.browser.state.store.BrowserStore | ||
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage | ||
import mozilla.components.support.test.ext.joinBlocking | ||
import mozilla.components.support.test.mock | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.mockito.Mockito.verify | ||
|
||
@RunWith(AndroidJUnit4::class) | ||
class ThumbnailsMiddlewareTest { | ||
|
||
@Test | ||
fun `thumbnail storage stores the provided thumbnail on update thumbnail action`() { | ||
val sessionIdOrUrl = "test-tab1" | ||
val tab = createTab("https://www.mozilla.org", id = "test-tab1") | ||
val thumbnailStorage: ThumbnailStorage = mock() | ||
val store = BrowserStore( | ||
initialState = BrowserState(tabs = listOf(tab)), | ||
middleware = listOf(ThumbnailsMiddleware(thumbnailStorage)) | ||
) | ||
|
||
val bitmap: Bitmap = mock() | ||
store.dispatch(ContentAction.UpdateThumbnailAction(sessionIdOrUrl, bitmap)).joinBlocking() | ||
verify(thumbnailStorage).saveThumbnail(sessionIdOrUrl, bitmap) | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
.../thumbnails/src/test/java/mozilla/components/browser/thumbnails/ThumbnailsUseCasesTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* 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 | ||
|
||
import android.graphics.Bitmap | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import kotlinx.coroutines.CompletableDeferred | ||
import kotlinx.coroutines.runBlocking | ||
import mozilla.components.browser.state.state.BrowserState | ||
import mozilla.components.browser.state.state.createTab | ||
import mozilla.components.browser.state.store.BrowserStore | ||
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage | ||
import mozilla.components.support.test.any | ||
import mozilla.components.support.test.mock | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.mockito.Mockito.`when` | ||
import org.mockito.Mockito.verify | ||
|
||
@RunWith(AndroidJUnit4::class) | ||
class ThumbnailsUseCasesTest { | ||
|
||
@Test | ||
fun `LoadThumbnailUseCase - loads the thumbnail from the in-memory ContentState if available`() = runBlocking { | ||
val sessionIdOrUrl = "test-tab1" | ||
val bitmap: Bitmap = mock() | ||
val tab = createTab("https://www.mozilla.org", id = "test-tab1", thumbnail = bitmap) | ||
val thumbnailStorage: ThumbnailStorage = mock() | ||
val store = BrowserStore( | ||
initialState = BrowserState(tabs = listOf(tab)), | ||
middleware = listOf(ThumbnailsMiddleware(thumbnailStorage)) | ||
) | ||
val useCases = ThumbnailsUseCases(store, thumbnailStorage) | ||
|
||
val thumbnail = useCases.loadThumbnail(sessionIdOrUrl) | ||
assertEquals(bitmap, thumbnail) | ||
} | ||
|
||
@Test | ||
fun `LoadThumbnailUseCase - loads the thumbnail from the disk cache if in-memory thumbnail is unavailable`() = runBlocking { | ||
val sessionIdOrUrl = "test-tab1" | ||
val bitmap: Bitmap = mock() | ||
val tab = createTab("https://www.mozilla.org", id = "test-tab1") | ||
val thumbnailStorage: ThumbnailStorage = mock() | ||
val store = BrowserStore( | ||
initialState = BrowserState(tabs = listOf(tab)), | ||
middleware = listOf(ThumbnailsMiddleware(thumbnailStorage)) | ||
) | ||
val useCases = ThumbnailsUseCases(store, thumbnailStorage) | ||
|
||
`when`(thumbnailStorage.loadThumbnail(any())).thenReturn(CompletableDeferred(bitmap)) | ||
|
||
val thumbnail = useCases.loadThumbnail(sessionIdOrUrl) | ||
verify(thumbnailStorage).loadThumbnail(sessionIdOrUrl) | ||
assertEquals(bitmap, thumbnail) | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
...nails/src/test/java/mozilla/components/browser/thumbnails/storage/ThumbnailStorageTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* 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.storage | ||
|
||
import android.graphics.Bitmap | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import kotlinx.coroutines.CompletableDeferred | ||
import kotlinx.coroutines.runBlocking | ||
import mozilla.components.support.test.mock | ||
import mozilla.components.support.test.robolectric.testContext | ||
import org.junit.After | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertNotNull | ||
import org.junit.Assert.assertNull | ||
import org.junit.Before | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.mockito.Mockito.`when` | ||
import org.mockito.Mockito.spy | ||
|
||
@RunWith(AndroidJUnit4::class) | ||
class ThumbnailStorageTest { | ||
|
||
@Before | ||
@After | ||
fun cleanUp() { | ||
sharedDiskCache.clear(testContext) | ||
} | ||
|
||
@Test | ||
fun `saveThumbnail`() = runBlocking { | ||
val sessionIdOrUrl = "test-tab1" | ||
val bitmap: Bitmap = mock() | ||
val thumbnailStorage = spy(ThumbnailStorage(testContext)) | ||
var thumbnail = thumbnailStorage.loadThumbnail(sessionIdOrUrl).await() | ||
|
||
assertNull(thumbnail) | ||
|
||
thumbnailStorage.saveThumbnail(sessionIdOrUrl, bitmap) | ||
thumbnail = thumbnailStorage.loadThumbnail(sessionIdOrUrl).await() | ||
assertNotNull(thumbnail) | ||
} | ||
|
||
@Test | ||
fun `loadThumbnail`() = runBlocking { | ||
val sessionIdOrUrl = "test-tab1" | ||
val bitmap: Bitmap = mock() | ||
val thumbnailStorage = spy(ThumbnailStorage(testContext)) | ||
|
||
thumbnailStorage.saveThumbnail(sessionIdOrUrl, bitmap) | ||
`when`(thumbnailStorage.loadThumbnail(sessionIdOrUrl)).thenReturn(CompletableDeferred(bitmap)) | ||
|
||
val thumbnail = thumbnailStorage.loadThumbnail(sessionIdOrUrl).await() | ||
assertEquals(bitmap, thumbnail) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.