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