Skip to content

Commit

Permalink
Issue mozilla-mobile#6827: Move Thumbnails into its own browser compo…
Browse files Browse the repository at this point in the history
…nent
  • Loading branch information
gabrielluong committed May 1, 2020
1 parent 4d123b1 commit 90df287
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .buildconfig.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ projects:
path: components/browser/tabstray
description: 'A tabs tray component for browsers.'
publish: true
browser-thumbnails:
path: components/browser/thumbnails
description: 'A component for loading and storing website thumbnails.'
publish: true
browser-toolbar:
path: components/browser/toolbar
description: 'A customizable toolbar for browsers.'
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ High-level components for building browser(-like) apps.

*[**Tabstray**](components/browser/tabstray/README.md) - A customizable tabs tray for browsers.

* 🔴 [**Thumbnails**](components/browser/thumbnails/README.md) - A component for loading and storing website thumbnails (screenshot of the website).

* 🔵 [**Toolbar**](components/browser/toolbar/README.md) - A customizable toolbar for browsers.

## Concept
Expand Down
19 changes: 19 additions & 0 deletions components/browser/thumbnails/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# [Android Components](../../../README.md) > Browser > Thumbnails

A component for loading and storing website thumbnails (screenshot of the website).

## Usage

### Setting up the dependency

Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)):

```Groovy
implementation "org.mozilla.components:browser-thumbnails:{latest-version}"
```

## License

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/
43 changes: 43 additions & 0 deletions components/browser/thumbnails/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* 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/. */

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion config.compileSdkVersion

defaultConfig {
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

packagingOptions {
exclude 'META-INF/proguard/androidx-annotations.pro'
}
}

dependencies {
implementation project(':browser-session')
implementation project(':concept-engine')
implementation project(':support-ktx')

implementation Dependencies.androidx_annotation

testImplementation project(':support-test')

testImplementation Dependencies.androidx_test_junit
testImplementation Dependencies.testing_mockito
}

apply from: '../../../publish.gradle'
ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description)
21 changes: 21 additions & 0 deletions components/browser/thumbnails/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
5 changes: 5 additions & 0 deletions components/browser/thumbnails/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- 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/. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="mozilla.components.browser.thumbnails" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* 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.content.Context
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.session.SelectionAwareSessionObserver
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineView
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.ktx.android.content.isOSOnLowMemory

/**
* Feature implementation for automatically taking thumbnails of sites.
* The feature will take a screenshot when the page finishes loading,
* and will add it to the [Session.thumbnail] property.
*
* If the OS is under low memory conditions, the screenshot will be not taken.
* Ideally, this should be used in conjunction with [SessionManager.onLowMemory] to allow
* free up some [Session.thumbnail] from memory.
*/
class BrowserThumbnails(
private val context: Context,
private val engineView: EngineView,
sessionManager: SessionManager
) : LifecycleAwareFeature {

private val observer = ThumbnailsRequestObserver(sessionManager)

/**
* Starts observing the selected session to listen for when a session finish loading.
*/
override fun start() {
observer.observeSelected()
}

/**
* Stops observing the selected session.
*/
override fun stop() {
observer.stop()
}

internal inner class ThumbnailsRequestObserver(
sessionManager: SessionManager
) : SelectionAwareSessionObserver(sessionManager) {

override fun onLoadingStateChanged(session: Session, loading: Boolean) {
if (!loading) {
requestScreenshot(session)
}
}
}

private fun requestScreenshot(session: Session) {
if (!isLowOnMemory()) {
engineView.captureThumbnail {
session.thumbnail = it
}
} else {
session.thumbnail = null
}
}

@VisibleForTesting
internal var testLowMemory = false

private fun isLowOnMemory() = testLowMemory || context.isOSOnLowMemory()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* 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 androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineView
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify

@RunWith(AndroidJUnit4::class)
class BrowserThumbnailsTest {

private lateinit var mockSessionManager: SessionManager
private lateinit var mockEngineView: EngineView
private lateinit var thumbnails: BrowserThumbnails

@Before
fun setup() {
val engine = mock<Engine>()
mockSessionManager = spy(SessionManager(engine))
mockEngineView = mock()
thumbnails = BrowserThumbnails(testContext, mockEngineView, mockSessionManager)
}

@Test
fun `when feature is stop must not capture thumbnail when a site finish loading`() {
thumbnails.start()
thumbnails.stop()

val session = getSelectedSession()

session.notifyObservers {
onLoadingStateChanged(session, false)
}

verify(mockEngineView, never()).captureThumbnail(any())
}

@Test
fun `feature must capture thumbnail when a site finish loading`() {
thumbnails.start()

val session = getSelectedSession()

session.notifyObservers {
onLoadingStateChanged(session, false)
}

verify(mockEngineView).captureThumbnail(any())
}

@Test
fun `when a page is loaded and the os is in low memory condition none thumbnail should be captured`() {
thumbnails.start()

val session = getSelectedSession()
session.thumbnail = mock()

thumbnails.testLowMemory = true

session.notifyObservers {
onLoadingStateChanged(session, false)
}

verify(mockEngineView, never()).captureThumbnail(any())
assertNull(session.thumbnail)
}

private fun getSelectedSession(): Session {
val session = Session("https://www.mozilla.org")
mockSessionManager.add(session)
mockSessionManager.select(session)
return session
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mock-maker-inline
// This allows mocking final classes (classes are final by default in Kotlin)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sdk=28
1 change: 1 addition & 0 deletions samples/browser/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ dependencies {
implementation project(':browser-session')
implementation project(':browser-state')
implementation project(':browser-tabstray')
implementation project(':browser-thumbnails')
implementation project(':browser-toolbar')
implementation project(':browser-menu')
implementation project(':browser-storage-memory')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_browser.view.*
import mozilla.components.browser.thumbnails.BrowserThumbnails
import mozilla.components.feature.awesomebar.AwesomeBarFeature
import mozilla.components.feature.awesomebar.provider.SearchSuggestionProvider
import mozilla.components.feature.search.SearchFeature
import mozilla.components.feature.session.ThumbnailsFeature
import mozilla.components.feature.tabs.WindowFeature
import mozilla.components.feature.tabs.toolbar.TabsToolbarFeature
import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature
Expand All @@ -26,7 +26,7 @@ import org.mozilla.samples.browser.integration.ReaderViewIntegration
* Fragment used for browsing the web within the main app.
*/
class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
private val thumbnailsFeature = ViewBoundFeatureWrapper<ThumbnailsFeature>()
private val thumbnailsFeature = ViewBoundFeatureWrapper<BrowserThumbnails>()
private val readerViewFeature = ViewBoundFeatureWrapper<ReaderViewIntegration>()
private val webExtToolbarFeature = ViewBoundFeatureWrapper<WebExtensionToolbarFeature>()
private val searchFeature = ViewBoundFeatureWrapper<SearchFeature>()
Expand Down Expand Up @@ -81,7 +81,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
)

thumbnailsFeature.set(
feature = ThumbnailsFeature(requireContext(), layout.engineView, components.sessionManager),
feature = BrowserThumbnails(requireContext(), layout.engineView, components.sessionManager),
owner = this,
view = layout
)
Expand Down

0 comments on commit 90df287

Please sign in to comment.