# HG changeset patch # User Da Risk # Date 1669758530 14400 # Node ID ab8851704ae740e6d89be426770ac1111af28bbc # Parent ed6a07c575e6bf05150884817066ad8dcbbb2e81 geekdroid: add LicenseInfoRepository and OssLicenseParser diff -r ed6a07c575e6 -r ab8851704ae7 geekdroid-firebase/build.gradle.kts --- 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") diff -r ed6a07c575e6 -r ab8851704ae7 geekdroid/build.gradle.kts --- 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") diff -r ed6a07c575e6 -r ab8851704ae7 geekdroid/src/main/java/com/geekorum/geekdroid/network/BrowserLauncher.kt --- 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): List } diff -r ed6a07c575e6 -r ab8851704ae7 geekdroid/src/main/java/com/geekorum/geekdroid/osslicenses/LicenseInfoRepository.kt --- /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 . + */ +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? = null + + suspend fun getLicensesInfo(): Map = 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 + } + } +} diff -r ed6a07c575e6 -r ab8851704ae7 geekdroid/src/main/java/com/geekorum/geekdroid/osslicenses/OssLicenseParser.kt --- /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 . + */ +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 { + 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 { + 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)