[fenix] Issue https://github.com/mozilla-mobile/fenix/issues/7425 (et al): Cache the list of installed browsers
Cache the list of installed browsers. Calling `Browsers.all` the application directly redundantly recalculates the list. Accessing the list of installed browsers through this cache will reduce that overhead.pull/600/head
parent
dac9353a73
commit
0b7436f2c0
@ -0,0 +1,46 @@
|
|||||||
|
/* 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 org.mozilla.fenix.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import mozilla.components.support.utils.Browsers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches the list of browsers installed on a user's device.
|
||||||
|
*
|
||||||
|
* BrowsersCache caches the list of installed browsers is gathered lazily when it is first accessed
|
||||||
|
* after initial creation or invalidation. For that reason, a context is required every time
|
||||||
|
* the cache is accessed.
|
||||||
|
*
|
||||||
|
* Users are responsible for invalidating the cache at the appropriate time. It is left up to the
|
||||||
|
* user to determine appropriate policies for maintaining the validity of the cache. If, when the
|
||||||
|
* cache is accessed, it is filled, the contents will be returned. As mentioned above, the cache
|
||||||
|
* will be lazily refilled after invalidation. In other words, invalidation is O(1).
|
||||||
|
*
|
||||||
|
* This cache is threadsafe.
|
||||||
|
*/
|
||||||
|
object BrowsersCache {
|
||||||
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
|
internal var cachedBrowsers: Browsers? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun all(context: Context): Browsers {
|
||||||
|
run {
|
||||||
|
val cachedBrowsers = cachedBrowsers
|
||||||
|
if (cachedBrowsers != null) {
|
||||||
|
return cachedBrowsers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Browsers.all(context).also {
|
||||||
|
this.cachedBrowsers = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun resetAll() {
|
||||||
|
cachedBrowsers = null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
/* 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/. */
|
||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package org.mozilla.fenix.utils
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.ResolveInfo
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import mozilla.components.support.test.robolectric.testContext
|
||||||
|
import mozilla.components.support.utils.Browsers
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.TestApplication
|
||||||
|
import org.robolectric.Shadows.shadowOf
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@Config(application = TestApplication::class)
|
||||||
|
class BrowsersCacheTest {
|
||||||
|
|
||||||
|
// NB: There is always one more browser than pretendBrowsersAreInstalled installs because
|
||||||
|
// the application we are testing is recognized as a browser itself!
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cached list of browsers match before-after installation when cache is not invalidated`() {
|
||||||
|
BrowsersCache.resetAll()
|
||||||
|
pretendBrowsersAreInstalled(
|
||||||
|
browsers = listOf(
|
||||||
|
Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName,
|
||||||
|
Browsers.KnownBrowser.REFERENCE_BROWSER.packageName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val initialBrowserList = BrowsersCache.all(testContext)
|
||||||
|
assertEquals(3, initialBrowserList.installedBrowsers.size)
|
||||||
|
|
||||||
|
pretendBrowsersAreInstalled(
|
||||||
|
browsers = listOf(
|
||||||
|
Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName,
|
||||||
|
Browsers.KnownBrowser.FIREFOX.packageName,
|
||||||
|
Browsers.KnownBrowser.CHROME.packageName,
|
||||||
|
Browsers.KnownBrowser.SAMSUNG_INTERNET.packageName,
|
||||||
|
Browsers.KnownBrowser.DUCKDUCKGO.packageName,
|
||||||
|
Browsers.KnownBrowser.REFERENCE_BROWSER.packageName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val updatedBrowserList = BrowsersCache.all(testContext)
|
||||||
|
assertEquals(3, updatedBrowserList.installedBrowsers.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cached list of browsers change before-after installation when cache is invalidated`() {
|
||||||
|
BrowsersCache.resetAll()
|
||||||
|
pretendBrowsersAreInstalled(
|
||||||
|
browsers = listOf(
|
||||||
|
Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName,
|
||||||
|
Browsers.KnownBrowser.REFERENCE_BROWSER.packageName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val initialBrowserList = BrowsersCache.all(testContext)
|
||||||
|
assertEquals(3, initialBrowserList.installedBrowsers.size)
|
||||||
|
|
||||||
|
pretendBrowsersAreInstalled(
|
||||||
|
browsers = listOf(
|
||||||
|
Browsers.KnownBrowser.FIREFOX_NIGHTLY.packageName,
|
||||||
|
Browsers.KnownBrowser.FIREFOX.packageName,
|
||||||
|
Browsers.KnownBrowser.CHROME.packageName,
|
||||||
|
Browsers.KnownBrowser.SAMSUNG_INTERNET.packageName,
|
||||||
|
Browsers.KnownBrowser.DUCKDUCKGO.packageName,
|
||||||
|
Browsers.KnownBrowser.REFERENCE_BROWSER.packageName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
BrowsersCache.resetAll()
|
||||||
|
|
||||||
|
val updatedBrowserList = BrowsersCache.all(testContext)
|
||||||
|
assertEquals(7, updatedBrowserList.installedBrowsers.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resetting the cache should empty it`() {
|
||||||
|
BrowsersCache.resetAll()
|
||||||
|
|
||||||
|
BrowsersCache.all(testContext)
|
||||||
|
|
||||||
|
assertNotNull(BrowsersCache.cachedBrowsers)
|
||||||
|
|
||||||
|
BrowsersCache.resetAll()
|
||||||
|
|
||||||
|
assertNull(BrowsersCache.cachedBrowsers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pretendBrowsersAreInstalled was taken, verbatim, from a-c.
|
||||||
|
// See support/utils/src/test/java/mozilla/components/support/utils/BrowsersTest.kt
|
||||||
|
private fun pretendBrowsersAreInstalled(
|
||||||
|
browsers: List<String> = listOf(),
|
||||||
|
defaultBrowser: String? = null,
|
||||||
|
url: String = "http://www.mozilla.org",
|
||||||
|
browsersExported: Boolean = true,
|
||||||
|
defaultBrowserExported: Boolean = true
|
||||||
|
) {
|
||||||
|
val packageManager = testContext.packageManager
|
||||||
|
val shadow = shadowOf(packageManager)
|
||||||
|
|
||||||
|
browsers.forEach { packageName ->
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.`package` = packageName
|
||||||
|
intent.data = Uri.parse(url)
|
||||||
|
|
||||||
|
val packageInfo = PackageInfo().apply {
|
||||||
|
this.packageName = packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
shadow.installPackage(packageInfo)
|
||||||
|
|
||||||
|
val activityInfo = ActivityInfo().apply {
|
||||||
|
exported = browsersExported
|
||||||
|
this.packageName = packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
val resolveInfo = ResolveInfo().apply {
|
||||||
|
resolvePackageName = packageName
|
||||||
|
this.activityInfo = activityInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
shadow.addResolveInfoForIntent(intent, resolveInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultBrowser != null) {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.data = Uri.parse(url)
|
||||||
|
|
||||||
|
val activityInfo = ActivityInfo().apply {
|
||||||
|
exported = defaultBrowserExported
|
||||||
|
packageName = defaultBrowser
|
||||||
|
}
|
||||||
|
|
||||||
|
val resolveInfo = ResolveInfo().apply {
|
||||||
|
resolvePackageName = defaultBrowser
|
||||||
|
this.activityInfo = activityInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
shadow.addResolveInfoForIntent(intent, resolveInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue