From eea1f2d38eeb4bb1b2f3ae607520fe8af7332aa8 Mon Sep 17 00:00:00 2001 From: kawaiiDango <1066519+kawaiiDango@users.noreply.github.com> Date: Sat, 17 Aug 2024 09:31:48 +0530 Subject: [PATCH] minor fixes, update deps --- app/build.gradle.kts | 6 +- .../main/java/com/arn/scrobble/NLService.kt | 32 ++++++--- .../arn/scrobble/api/file/FileScrobblable.kt | 2 +- .../java/com/arn/scrobble/billing/Security.kt | 70 +++++++++++++++++-- .../main/java/com/arn/scrobble/main/App.kt | 2 - .../com/arn/scrobble/pref/PrefFragment.kt | 10 +-- app/src/main/res/xml/locales_config.xml | 1 - gradle/libs.versions.toml | 10 +-- gradle/wrapper/gradle-wrapper.properties | 4 +- 9 files changed, 101 insertions(+), 36 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5aaa5b03..1b8d496f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -375,20 +375,20 @@ fun fetchCrowdinLanguages(projectId: String, token: String, minProgress: Int) { ).sorted() // write to locale_config.xml - val localesConfigText = """ - + val localesConfigText = + """ ${languagesFiltered.joinToString("\n") { " " }} """ file("src/main/res/xml/locales_config.xml").writeText(localesConfigText) + // write to LocaleUtils.kt val localeUtilsPartialText = """ val localesSet = arrayOf( ${languagesFiltered.joinToString("\n") { " \"$it\"," }} ) """ - // write to LocaleUtils.kt val localeUtilsFile = file("src/main/java/com/arn/scrobble/utils/LocaleUtils.kt") val localeUtilsText = localeUtilsFile.readText() diff --git a/app/src/main/java/com/arn/scrobble/NLService.kt b/app/src/main/java/com/arn/scrobble/NLService.kt index f216aa18..0b95e777 100644 --- a/app/src/main/java/com/arn/scrobble/NLService.kt +++ b/app/src/main/java/com/arn/scrobble/NLService.kt @@ -106,11 +106,19 @@ class NLService : NotificationListenerService() { // onCreate seems to get called only once in those cases. // also unreliable on lp and mm // just gate them with an inited flag - if (BuildConfig.DEBUG) - toast(R.string.scrobbler_on) - if (!inited) - init() + + if (!inited) { + job = SupervisorJob() + coroutineScope = CoroutineScope(Dispatchers.Main + job!!) + + // API 23 bug, force run them on Main thread + coroutineScope.launch { + if (BuildConfig.DEBUG) + toast(R.string.scrobbler_on) + init() + } + } } @@ -118,9 +126,6 @@ class NLService : NotificationListenerService() { // set it to true right away in case onListenerConnected gets called again before init has finished inited = true - job = SupervisorJob() - coroutineScope = CoroutineScope(Dispatchers.Main + job!!) - val filter = IntentFilter().apply { addAction(iCANCEL) addAction(iLOVE) @@ -240,22 +245,27 @@ class NLService : NotificationListenerService() { sessListener = null scrobbleQueue.shutdown() } - job?.cancel() PanoDb.destroyInstance() + job?.cancel() } override fun onListenerDisconnected() { //api 24+ only if (BuildConfig.DEBUG) toast(R.string.scrobbler_off) - if (inited && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + if (inited && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { destroy() + } } override fun onDestroy() { - if (inited && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) - destroy() + if (inited && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + // API 23 bug, force run them on Main thread + coroutineScope.launch { + destroy() + } + } super.onDestroy() } diff --git a/app/src/main/java/com/arn/scrobble/api/file/FileScrobblable.kt b/app/src/main/java/com/arn/scrobble/api/file/FileScrobblable.kt index 43ec7f14..cfd5cce5 100644 --- a/app/src/main/java/com/arn/scrobble/api/file/FileScrobblable.kt +++ b/app/src/main/java/com/arn/scrobble/api/file/FileScrobblable.kt @@ -311,7 +311,7 @@ class FileScrobblable(userAccount: UserAccountSerializable) : Scrobblable(userAc track = row[3], album = row[4], albumArtist = row[5], - durationMs = row[6].toLong(), + durationMs = row[6].toLongOrNull(), mediaPlayerPackage = row[7], mediaPlayerName = row.getOrNull(8), mediaPlayerVersion = row.getOrNull(9), diff --git a/app/src/main/java/com/arn/scrobble/billing/Security.kt b/app/src/main/java/com/arn/scrobble/billing/Security.kt index c6ab629a..c5703143 100644 --- a/app/src/main/java/com/arn/scrobble/billing/Security.kt +++ b/app/src/main/java/com/arn/scrobble/billing/Security.kt @@ -20,10 +20,13 @@ package com.arn.scrobble.billing * frauds. */ import android.content.pm.PackageManager -import android.util.Base64 import com.android.billingclient.api.Purchase import com.arn.scrobble.Tokens import com.arn.scrobble.main.App +import com.arn.scrobble.utils.Stuff +import io.ktor.util.decodeBase64Bytes +import io.ktor.util.decodeBase64String +import kotlinx.serialization.Serializable import java.io.IOException import java.security.* import java.security.spec.InvalidKeySpecException @@ -35,7 +38,8 @@ import java.security.spec.X509EncodedKeySpec */ object Security { private const val KEY_FACTORY_ALGORITHM = "RSA" - private const val SIGNATURE_ALGORITHM = "SHA1withRSA" + private const val PLAY_SIGNATURE_ALGORITHM = "SHA1withRSA" + private const val JWT_SIGNATURE_ALGORITHM = "SHA256withRSA" /** * Verifies that the data was signed with the given signature @@ -53,7 +57,7 @@ object Security { ) { return false } - val key = generatePublicKey(Tokens.BASE_64_ENCODED_PUBLIC_KEY) + val key = loadPublicKey(Tokens.BASE_64_ENCODED_PUBLIC_KEY) return verify(key, purchase.originalJson, purchase.signature) } @@ -65,9 +69,9 @@ object Security { * is invalid */ @Throws(IOException::class) - private fun generatePublicKey(encodedPublicKey: String): PublicKey { + private fun loadPublicKey(encodedPublicKey: String): PublicKey { try { - val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT) + val decodedKey = encodedPublicKey.decodeBase64Bytes() val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM) return keyFactory.generatePublic(X509EncodedKeySpec(decodedKey)) } catch (e: NoSuchAlgorithmException) { @@ -90,12 +94,12 @@ object Security { private fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean { val signatureBytes: ByteArray try { - signatureBytes = Base64.decode(signature, Base64.DEFAULT) + signatureBytes = signature.decodeBase64Bytes() } catch (e: IllegalArgumentException) { return false } try { - val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM) + val signatureAlgorithm = Signature.getInstance(PLAY_SIGNATURE_ALGORITHM) signatureAlgorithm.initVerify(publicKey) signatureAlgorithm.update(signedData.toByteArray()) return signatureAlgorithm.verify(signatureBytes) @@ -122,4 +126,56 @@ object Security { android.os.Process.killProcess(android.os.Process.myPid()) return null } + + @OptIn(ExperimentalStdlibApi::class) + fun sha256(s: String) = + MessageDigest.getInstance("SHA-256") + .digest(s.toByteArray()) + .toHexString() + + fun validateJwt(token: String, base64PublicKey: String): Boolean { + @Serializable + data class JwtHeader(val alg: String, val typ: String) + + try { + val parts = token.split(".") + if (parts.size != 3) return false + + val header = parts[0].decodeBase64String().let { + Stuff.myJson.decodeFromString(it) + } + + if (header.alg != "RS256") return false +// val payload = String(Base64.decode(parts[1], Base64.DEFAULT)) + val signature = parts[2].decodeBase64Bytes() + + val data = "${parts[0]}.${parts[1]}".toByteArray() + + val trimmedKey = base64PublicKey.replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replace("\\s".toRegex(), "") + + val publicKey = loadPublicKey(trimmedKey) + verifySignature(data, signature, publicKey) + } catch (e: Exception) { + return false + } + return true + } + + private fun verifySignature( + data: ByteArray, + signature: ByteArray, + publicKey: PublicKey + ): Boolean { + return try { + val sig = Signature.getInstance("SHA256withRSA") + sig.initVerify(publicKey) + sig.update(data) + sig.verify(signature) + } catch (e: Exception) { + false + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/arn/scrobble/main/App.kt b/app/src/main/java/com/arn/scrobble/main/App.kt index a88abc9b..e6dbbc5a 100644 --- a/app/src/main/java/com/arn/scrobble/main/App.kt +++ b/app/src/main/java/com/arn/scrobble/main/App.kt @@ -16,7 +16,6 @@ import androidx.work.Configuration import coil3.ImageLoader import coil3.PlatformContext import coil3.SingletonImageLoader -import coil3.networkObserverEnabled import coil3.request.allowHardware import coil3.request.crossfade import coil3.size.Precision @@ -158,7 +157,6 @@ class App : Application(), SingletonImageLoader.Factory, Configuration.Provider .crossfade(true) .precision(Precision.INEXACT) .allowHardware(false) - .networkObserverEnabled(true) .build() fun clearMusicEntryImageCache(entry: MusicEntry) { diff --git a/app/src/main/java/com/arn/scrobble/pref/PrefFragment.kt b/app/src/main/java/com/arn/scrobble/pref/PrefFragment.kt index a6dcf6d2..5aad0e03 100644 --- a/app/src/main/java/com/arn/scrobble/pref/PrefFragment.kt +++ b/app/src/main/java/com/arn/scrobble/pref/PrefFragment.kt @@ -162,7 +162,7 @@ class PrefFragment : PreferenceFragmentCompat() { val changeLocalePref = findPreference(MainPrefs.PREF_LOCALE)!! var prevLang = "" - val localeEntryValues = arrayOf("auto") + LocaleUtils.localesSet.toTypedArray() + val localeEntryValues = arrayOf("auto") + LocaleUtils.localesSet val localeEntries = localeEntryValues.map { if (it == "auto") return@map getString(R.string.auto) @@ -300,8 +300,8 @@ class PrefFragment : PreferenceFragmentCompat() { .title = getString(R.string.s_top_scrobbles, getString(R.string.monthly)) val chartsWidget = findPreference("charts_widget")!! - chartsWidget.setOnPreferenceClickListener { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + chartsWidget.setOnPreferenceClickListener { val appWidgetManager = AppWidgetManager.getInstance(requireContext()) if (appWidgetManager.isRequestPinAppWidgetSupported) { val pi = PendingIntent.getActivity( @@ -316,8 +316,10 @@ class PrefFragment : PreferenceFragmentCompat() { ComponentName(requireContext(), ChartsWidgetProvider::class.java) appWidgetManager.requestPinAppWidget(myProvider, null, pi) } + true } - true + } else { + chartsWidget.isVisible = false } hideOnTV += chartsWidget diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index 3b9b44a9..9a3685f3 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -1,4 +1,3 @@ - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 20a41478..1c920991 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,11 +2,11 @@ targetSdk = "35" minSdk = "23" minSdkBaselineProfile = "28" -android-application = "8.7.0-alpha05" +android-application = "8.7.0-alpha07" aboutlibraries = "11.2.2" activity = "1.9.1" androidSnowfall = "1.2.1" -coil = "3.0.0-alpha09" +coil = "3.0.0-alpha10" constraintlayout = "2.2.0-alpha14" billing = "7.0.0" core = "1.15.0-alpha01" @@ -20,7 +20,7 @@ fragment = "1.8.2" harmony = "1.2.6" junit = "4.13.2" kotlin = "2.0.10" -kotlinCsvJvm = "1.9.3" +kotlinCsvJvm = "1.10.0" kotlinx-serialization-json = "1.7.1" ktorBom = "3.0.0-beta-2" kumo-core = "1.28.1" @@ -37,7 +37,7 @@ play-publisher = "3.10.1" preference-ktx = "1.2.1" profileinstaller = "1.4.0-alpha02" recyclerview = "1.4.0-alpha02" -runner = "1.6.1" +runner = "1.6.2" skeletonlayout = "5.0.0" timber = "5.0.1" swiperefreshlayout = "1.2.0-alpha01" @@ -60,7 +60,7 @@ android-snowfall = { module = "com.github.jetradarmobile:android-snowfall", vers androidx-activity = { module = "androidx.activity:activity", version.ref = "activity" } androidx-viewpager = { module = "androidx.viewpager:viewpager", version.ref = "viewpager" } desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" } -kotlin-csv-jvm = { module = "com.github.doyaaaaaken:kotlin-csv-jvm", version.ref = "kotlinCsvJvm" } +kotlin-csv-jvm = { module = "com.jsoizo:kotlin-csv-jvm", version.ref = "kotlinCsvJvm" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } ktor-bom = { module = "io.ktor:ktor-bom", version.ref = "ktorBom" } ktor-client-core = { module = "io.ktor:ktor-client-core" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 30d8f448..2a00ed2a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Jul 14 06:10:57 IST 2024 +#Thu Aug 15 09:00:19 IST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists