diff --git a/app/build.gradle b/app/build.gradle
index 2035810be..0fc4e940c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -330,7 +330,6 @@ dependencies {
implementation Deps.androidx_coordinatorlayout
implementation Deps.sentry
- implementation Deps.osslicenses_library
implementation Deps.leanplum_core
implementation Deps.leanplum_fcm
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 825889326..554e11126 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -228,13 +228,8 @@
-
-
-
+
diff --git a/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt
index ece022c02..3fd152943 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt
@@ -13,7 +13,6 @@ import android.view.ViewGroup
import androidx.core.content.pm.PackageInfoCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
-import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import kotlinx.android.synthetic.main.fragment_about.*
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig
@@ -168,13 +167,8 @@ class AboutFragment : Fragment(), AboutPageListener {
}
private fun openLibrariesPage() {
- startActivity(Intent(context, OssLicensesMenuActivity::class.java))
- OssLicensesMenuActivity.setActivityTitle(
- getString(
- R.string.open_source_licenses_title,
- appName
- )
- )
+ val intent = Intent(requireContext(), AboutLibrariesActivity::class.java)
+ startActivity(intent)
}
override fun onAboutItemClicked(item: AboutItem) {
diff --git a/app/src/main/java/org/mozilla/fenix/settings/about/AboutLibrariesActivity.kt b/app/src/main/java/org/mozilla/fenix/settings/about/AboutLibrariesActivity.kt
new file mode 100644
index 000000000..bf69fb2f3
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/settings/about/AboutLibrariesActivity.kt
@@ -0,0 +1,120 @@
+/* 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.settings.about
+
+import android.graphics.Typeface
+import android.os.Bundle
+import android.text.util.Linkify
+import android.widget.ArrayAdapter
+import android.widget.ListView
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import org.mozilla.fenix.R
+import java.nio.charset.Charset
+import java.util.Locale
+
+/**
+ * Displays the licenses of all the libraries used by Fenix.
+ *
+ * This is a re-implementation of play-services-oss-licenses library.
+ * We can't use the official implementation in the OSS flavor of Fenix
+ * because it is proprietary and closed-source.
+ *
+ * There are popular FLOSS alternatives to Google's plugin and library
+ * such as AboutLibraries (https://github.com/mikepenz/AboutLibraries)
+ * but we considered the risk of introducing such third-party dependency
+ * to Fenix too high. Therefore, we use Google's gradle plugin to
+ * extract the dependencies and their licenses, and this activity
+ * to show the extracted licenses to the end-user.
+ */
+class AboutLibrariesActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val appName = getString(R.string.app_name)
+ title = getString(R.string.open_source_licenses_title, appName)
+ setContentView(R.layout.about_libraries_activity)
+
+ setSupportActionBar(findViewById(R.id.toolbar))
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ supportActionBar?.setDisplayShowHomeEnabled(true)
+
+ setupLibrariesListView()
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ onBackPressed()
+ return true
+ }
+
+ private fun setupLibrariesListView() {
+ val libraries = parseLibraries()
+ val listView = findViewById(R.id.about_libraries_listview)
+ listView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, libraries)
+ listView.setOnItemClickListener { _, _, position, _ ->
+ showLicenseDialog(libraries[position])
+ }
+ }
+
+ private fun parseLibraries(): List {
+ /*
+ The gradle plugin "oss-licenses-plugin" creates two "raw" resources:
+
+ - third_party_licenses which is the binary concatenation of all the licenses text for
+ all the libraries. License texts can either be an URL to a license file or just the
+ raw text of the license.
+
+ - third_party_licenses_metadata which contains one dependency per line formatted in
+ the following way: "[start_offset]:[length] [name]"
+
+ [start_offset] : first byte in third_party_licenses that contains the license
+ text for this library.
+ [length] : length of the license text for this library in
+ third_party_licenses.
+ [name] : either the name of the library, or its artifact name.
+
+ See https://github.com/google/play-services-plugins/tree/master/oss-licenses-plugin
+ */
+ val licensesData = resources
+ .openRawResource(R.raw.third_party_licenses)
+ .readBytes()
+ val licensesMetadataReader = resources
+ .openRawResource(R.raw.third_party_license_metadata)
+ .bufferedReader()
+
+ return licensesMetadataReader.use { reader -> reader.readLines() }.map { line ->
+ val (section, name) = line.split(" ", limit = 2)
+ val (startOffset, length) = section.split(":", limit = 2).map(String::toInt)
+ val licenseData = licensesData.sliceArray(startOffset until startOffset + length)
+ val licenseText = licenseData.toString(Charset.forName("UTF-8"))
+ LibraryItem(name, licenseText)
+ }.sortedBy { item -> item.name.toLowerCase(Locale.ROOT) }
+ }
+
+ private fun showLicenseDialog(libraryItem: LibraryItem) {
+ val dialog = AlertDialog.Builder(this)
+ .setTitle(libraryItem.name)
+ .setMessage(libraryItem.license)
+ .create()
+ dialog.show()
+
+ val textView = dialog.findViewById(android.R.id.message)!!
+ Linkify.addLinks(textView, Linkify.ALL)
+ textView.linksClickable = true
+ textView.textSize = LICENSE_TEXT_SIZE
+ textView.typeface = Typeface.MONOSPACE
+ }
+
+ companion object {
+ private const val LICENSE_TEXT_SIZE = 10F
+ }
+}
+
+private class LibraryItem(val name: String, val license: String) {
+ override fun toString(): String {
+ return name
+ }
+}
diff --git a/app/src/main/res/layout/about_libraries_activity.xml b/app/src/main/res/layout/about_libraries_activity.xml
new file mode 100644
index 000000000..bf23bb437
--- /dev/null
+++ b/app/src/main/res/layout/about_libraries_activity.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/app/src/test/java/org/mozilla/fenix/settings/about/AboutLibrariesActivityTest.kt b/app/src/test/java/org/mozilla/fenix/settings/about/AboutLibrariesActivityTest.kt
new file mode 100644
index 000000000..140dc44ae
--- /dev/null
+++ b/app/src/test/java/org/mozilla/fenix/settings/about/AboutLibrariesActivityTest.kt
@@ -0,0 +1,45 @@
+/* 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.settings.about
+
+import android.widget.ListView
+import android.widget.TextView
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.R
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+import org.robolectric.Robolectric
+import org.robolectric.Shadows.shadowOf
+import org.robolectric.shadows.ShadowAlertDialog
+
+@RunWith(FenixRobolectricTestRunner::class)
+class AboutLibrariesActivityTest {
+ @Test
+ fun `activity should display licenses`() {
+ val activity = Robolectric.buildActivity(AboutLibrariesActivity::class.java).create().get()
+ val listView = activity.findViewById(R.id.about_libraries_listview)
+
+ assertTrue(0 < listView.count)
+ }
+
+ @Test
+ fun `item click should open license dialog`() {
+ val activity = Robolectric.buildActivity(AboutLibrariesActivity::class.java).create().get()
+
+ val listView = activity.findViewById(R.id.about_libraries_listview)
+ val listViewShadow = shadowOf(listView)
+ listViewShadow.clickFirstItemContainingText("org.mozilla.geckoview:geckoview")
+
+ val alertDialogShadow = ShadowAlertDialog.getLatestDialog()
+ assertTrue(alertDialogShadow.isShowing)
+
+ val alertDialogText = alertDialogShadow
+ .findViewById(android.R.id.message)
+ .text
+ .toString()
+ assertTrue(alertDialogText.contains("MPL"))
+ }
+}
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
index 876e537dd..40933b3c0 100644
--- a/buildSrc/src/main/java/Dependencies.kt
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -10,7 +10,6 @@ object Versions {
const val leakcanary = "2.4"
const val leanplum = "5.4.0"
const val osslicenses_plugin = "0.9.5"
- const val osslicenses_library = "17.0.0"
const val detekt = "1.9.1"
const val androidx_appcompat = "1.2.0-rc01"
@@ -59,7 +58,6 @@ object Deps {
const val allopen = "org.jetbrains.kotlin:kotlin-allopen:${Versions.kotlin}"
const val osslicenses_plugin = "com.google.android.gms:oss-licenses-plugin:${Versions.osslicenses_plugin}"
- const val osslicenses_library = "com.google.android.gms:play-services-oss-licenses:${Versions.osslicenses_library}"
const val mozilla_concept_engine = "org.mozilla.components:concept-engine:${Versions.mozilla_android_components}"
const val mozilla_concept_menu = "org.mozilla.components:concept-menu:${Versions.mozilla_android_components}"