geekdroid: add LicenseInfoRepository and OssLicenseParser
authorDa Risk <da_risk@geekorum.com>
Tue, 29 Nov 2022 17:48:50 -0400
changeset 23 ab8851704ae7
parent 22 ed6a07c575e6
child 24 45a1084ed99c
geekdroid: add LicenseInfoRepository and OssLicenseParser
geekdroid-firebase/build.gradle.kts
geekdroid/build.gradle.kts
geekdroid/src/main/java/com/geekorum/geekdroid/network/BrowserLauncher.kt
geekdroid/src/main/java/com/geekorum/geekdroid/osslicenses/LicenseInfoRepository.kt
geekdroid/src/main/java/com/geekorum/geekdroid/osslicenses/OssLicenseParser.kt
--- a/geekdroid-firebase/build.gradle.kts	Tue Nov 29 15:05:58 2022 -0400
+++ b/geekdroid-firebase/build.gradle.kts	Tue Nov 29 17:48:50 2022 -0400
@@ -40,7 +40,7 @@
     implementation(enforcedPlatform(kotlin("bom")))
     implementation(kotlin("stdlib-jdk8"))
 
-    implementation(enforcedPlatform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.3.5"))
+    implementation(enforcedPlatform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
     api("org.jetbrains.kotlinx:kotlinx-coroutines-core")
     api("org.jetbrains.kotlinx:kotlinx-coroutines-play-services")
 
--- a/geekdroid/build.gradle.kts	Tue Nov 29 15:05:58 2022 -0400
+++ b/geekdroid/build.gradle.kts	Tue Nov 29 17:48:50 2022 -0400
@@ -86,7 +86,7 @@
     implementation(enforcedPlatform(kotlin("bom")))
     implementation(kotlin("stdlib-jdk8"))
 
-    implementation(enforcedPlatform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.3.5"))
+    implementation(enforcedPlatform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
 
     implementation("androidx.lifecycle:lifecycle-livedata-core-ktx:2.5.1")
--- a/geekdroid/src/main/java/com/geekorum/geekdroid/network/BrowserLauncher.kt	Tue Nov 29 15:05:58 2022 -0400
+++ b/geekdroid/src/main/java/com/geekorum/geekdroid/network/BrowserLauncher.kt	Tue Nov 29 17:48:50 2022 -0400
@@ -136,11 +136,11 @@
         launchUrl(context, uri) { customizer?.customize(this) }
     }
 
-    interface LaunchCustomizer {
+    fun interface LaunchCustomizer {
         fun customize(builder: CustomTabsIntent.Builder)
     }
 
-    interface PreferredPackageSelector {
+    fun interface PreferredPackageSelector {
         fun orderByPreference(availablePackages: List<String>): List<String>
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/geekdroid/src/main/java/com/geekorum/geekdroid/osslicenses/LicenseInfoRepository.kt	Tue Nov 29 17:48:50 2022 -0400
@@ -0,0 +1,63 @@
+/*
+ * Geekdroid is a utility library for development on the Android
+ * Platform.
+ *
+ * Copyright (C) 2017-2022 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekdroid.
+ *
+ * Geekdroid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Geekdroid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Geekdroid.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.geekdroid.osslicenses
+
+import android.content.Context
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+class LicenseInfoRepository(
+    private val appContext: Context,
+    private val mainCoroutineDispatcher: CoroutineDispatcher,
+    private val ioCoroutineDispatcher: CoroutineDispatcher,
+) {
+
+    private var licensesInfo: Map<String, String>? = null
+
+    suspend fun getLicensesInfo(): Map<String, String> = withContext(mainCoroutineDispatcher) {
+        parseLicenses()
+        checkNotNull(licensesInfo)
+    }
+
+    suspend fun getLicenseFor(dependency: String): String = withContext(mainCoroutineDispatcher) {
+        parseLicenses()
+        checkNotNull(licensesInfo).let {
+            return@withContext it[dependency] ?: error("Dependency not found")
+        }
+    }
+
+    private suspend fun parseLicenses() = withContext(mainCoroutineDispatcher) {
+        if (licensesInfo == null) {
+            val licenses = withContext(ioCoroutineDispatcher) {
+                OssLicenseParser.openDefaultThirdPartyLicenses(appContext).use  { licensesInput ->
+                    OssLicenseParser.openDefaultThirdPartyLicensesMetadata(appContext).use { licensesMetadataInput ->
+                        val parser = OssLicenseParser(
+                            thirdPartyLicensesInput = licensesInput,
+                            thirdPartyLicensesMetadataInput = licensesMetadataInput)
+                        parser.parseLicenses()
+                    }
+                }
+            }
+            licensesInfo = licenses
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/geekdroid/src/main/java/com/geekorum/geekdroid/osslicenses/OssLicenseParser.kt	Tue Nov 29 17:48:50 2022 -0400
@@ -0,0 +1,122 @@
+/*
+ * Geekdroid is a utility library for development on the Android
+ * Platform.
+ *
+ * Copyright (C) 2017-2022 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekdroid.
+ *
+ * Geekdroid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Geekdroid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Geekdroid.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.geekdroid.osslicenses
+
+import android.annotation.SuppressLint
+import android.content.Context
+import okio.ByteString
+import okio.buffer
+import okio.source
+import java.io.InputStream
+
+/**
+ * Parse licences data generated by the "com.google.android.gms.oss-licenses-plugin" gradle plugin.
+ * [thirdPartyLicensesInput] is usually res/raw/third_party_licenses file
+ * [thirdPartyLicensesMetadataInput] is usually res/raw/third_party_license_metadata file
+ */
+class OssLicenseParser(
+    private val thirdPartyLicensesInput: InputStream,
+    private val thirdPartyLicensesMetadataInput: InputStream
+) {
+    fun parseLicenses(): Map<String, String> {
+        val licenses = readLicensesFile()
+        return buildLicenseInfo(licenses)
+    }
+
+    private fun readLicensesFile(): ByteString {
+        return thirdPartyLicensesInput.source().use { source ->
+            source.buffer().use {
+                it.readByteString()
+            }
+        }
+    }
+
+    private fun buildLicenseInfo(license: ByteString): Map<String, String> {
+        return thirdPartyLicensesMetadataInput.source().use { source ->
+            source.buffer().use {
+                buildMap {
+                    while (true) {
+                        val line = it.readUtf8Line() ?: break
+                        if (line.isNotBlank()) {
+                            with(line.toLineParser()) {
+                                val start = readStartIdx()
+                                val length = readLength()
+                                val dependency = readName()
+                                val licenseTxt = license.substring(
+                                    beginIndex = start,
+                                    endIndex = start + length + 1
+                                ).string(Charsets.UTF_8)
+                                put(dependency, licenseTxt)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        @SuppressLint("DiscouragedApi")
+        fun openDefaultThirdPartyLicenses(context: Context): InputStream {
+            val thirdPartyLicensesId = context.resources.getIdentifier("third_party_licenses", "raw", context.packageName)
+            check(thirdPartyLicensesId != 0) { "third_party_licenses was not found in resources raw of ${context.packageName}"}
+            return context.resources.openRawResource(thirdPartyLicensesId)
+        }
+
+        @SuppressLint("DiscouragedApi")
+        fun openDefaultThirdPartyLicensesMetadata(context: Context): InputStream {
+            val thirdPartyLicensesMetadataId = context.resources.getIdentifier("third_party_license_metadata", "raw", context.packageName)
+            check(thirdPartyLicensesMetadataId != 0) { "third_party_license_metadata was not found in resources raw of ${context.packageName}"}
+            return context.resources.openRawResource(thirdPartyLicensesMetadataId)
+        }
+    }
+}
+
+private class LicenseMetadataLineParser(
+    private val line: String
+) {
+
+    private var idx = 0
+
+    fun readStartIdx(): Int {
+        val end = line.indexOf(':', startIndex = idx)
+        val result = line.substring(idx, end).toInt()
+        idx = end + 1
+        return result
+    }
+
+    fun readLength(): Int {
+        val end = line.indexOf(' ', startIndex = idx)
+        val result = line.substring(idx, end).toInt()
+        idx = end + 1
+        return result
+    }
+
+    fun readName(): String {
+        val result = line.substring(idx)
+        idx = line.length + 1
+        return result
+    }
+
+}
+
+private fun String.toLineParser() = LicenseMetadataLineParser(this)