From 4220fadf469284a43cd80dc905d0bd472613b0f9 Mon Sep 17 00:00:00 2001
From: Akshat Tiwari <51470769+akshaaatt@users.noreply.github.com>
Date: Mon, 6 Nov 2023 21:23:59 +0530
Subject: [PATCH 1/5] Bump dependencies, add strict mode to app and fix
codebase
---
app/build.gradle | 20 ++---
.../listenbrainz/android/application/App.kt | 77 ++++++++++++++++++-
.../ui/screens/dashboard/DashboardActivity.kt | 19 ++++-
.../listenbrainz/android/util/Constants.kt | 9 ++-
app/src/main/res/values/strings.xml | 28 +++++++
build.gradle | 8 +-
sharedTest/build.gradle | 4 +-
7 files changed, 142 insertions(+), 23 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index f2b4bb53..5e42ec83 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -123,7 +123,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
- implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.7.0-alpha02'
+ implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.7.0-beta01'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.browser:browser:1.6.0'
@@ -145,7 +145,7 @@ dependencies {
//Image downloading and Caching library
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation "com.github.bumptech.glide:compose:1.0.0-alpha.3"
- implementation 'io.coil-kt:coil-compose:2.4.0'
+ implementation 'io.coil-kt:coil-compose:2.5.0'
implementation 'com.caverock:androidsvg-aar:1.4'
ksp 'com.github.bumptech.glide:compiler:4.16.0'
@@ -153,24 +153,24 @@ dependencies {
implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"
//Design Setup
- implementation 'com.google.android.material:material:1.9.0'
+ implementation 'com.google.android.material:material:1.10.0'
implementation 'com.airbnb.android:lottie:6.1.0'
implementation 'com.github.akshaaatt:Onboarding:1.1.2'
implementation 'com.github.akshaaatt:Share-Android:1.0.0'
- implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
+ implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
implementation 'com.airbnb.android:lottie-compose:6.1.0'
//Dagger-Hilt
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
- kapt("androidx.hilt:hilt-compiler:1.0.0")
- implementation 'androidx.hilt:hilt-work:1.0.0'
+ kapt('androidx.hilt:hilt-compiler:1.1.0')
+ implementation 'androidx.hilt:hilt-work:1.1.0'
implementation "androidx.startup:startup-runtime:1.1.1"
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
//Jetpack Compose
- implementation platform('androidx.compose:compose-bom:2023.09.02')
+ implementation platform('androidx.compose:compose-bom:2023.10.01')
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-tooling'
@@ -191,7 +191,7 @@ dependencies {
implementation files('./lib/spotify-app-remote-release-0.7.2.aar')
// HTML Parser for retrieving token
- implementation "org.jsoup:jsoup:1.16.1"
+ implementation 'org.jsoup:jsoup:1.16.2'
//Socket IO
implementation ('io.socket:socket.io-client:2.1.0') {
@@ -207,11 +207,11 @@ dependencies {
testImplementation 'androidx.arch.core:core-testing:2.2.0'
// Mockito framework
- testImplementation 'org.mockito:mockito-core:5.5.0'
+ testImplementation 'org.mockito:mockito-core:5.7.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.1.0'
debugImplementation "androidx.test:monitor:1.6.1" // Solves "class PlatformTestStorageRegistery not found" error for ui tests.
- debugImplementation "androidx.compose.ui:ui-test-manifest:1.5.3"
+ debugImplementation 'androidx.compose.ui:ui-test-manifest:1.5.4'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
androidTestImplementation "androidx.work:work-testing:$work_version"
diff --git a/app/src/main/java/org/listenbrainz/android/application/App.kt b/app/src/main/java/org/listenbrainz/android/application/App.kt
index c2a55e5f..a92c7678 100644
--- a/app/src/main/java/org/listenbrainz/android/application/App.kt
+++ b/app/src/main/java/org/listenbrainz/android/application/App.kt
@@ -1,7 +1,12 @@
package org.listenbrainz.android.application
import android.app.Application
+import android.app.NotificationChannel
+import android.app.NotificationManager
import android.content.Intent
+import android.os.Build
+import android.os.StrictMode
+import androidx.core.content.ContextCompat
import androidx.hilt.work.HiltWorkerFactory
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
@@ -9,8 +14,11 @@ import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
+import org.listenbrainz.android.BuildConfig
+import org.listenbrainz.android.R
import org.listenbrainz.android.repository.preferences.AppPreferences
import org.listenbrainz.android.service.ListenScrobbleService
+import org.listenbrainz.android.util.Constants
import javax.inject.Inject
@HiltAndroidApp
@@ -24,6 +32,11 @@ class App : Application(), Configuration.Provider {
override fun onCreate() {
super.onCreate()
+
+ if (BuildConfig.DEBUG) {
+ enableStrictMode()
+ }
+
context = this
MainScope().launch {
@@ -35,8 +48,70 @@ class App : Application(), Configuration.Provider {
startListenService()
}
}
+
+ createChannels()
}
-
+
+ private fun createChannels() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+ return
+
+ val nm = ContextCompat.getSystemService(this, NotificationManager::class.java)!!
+
+ val channels = nm.notificationChannels
+
+ // delete old channels, if they exist
+ if (channels?.any { it.id == "foreground" } == true) {
+ channels.forEach { nm.deleteNotificationChannel(it.id) }
+ }
+
+ nm.createNotificationChannel(
+ NotificationChannel(
+ Constants.Strings.CHANNEL_NOTI_SCROBBLING,
+ getString(R.string.state_scrobbling), NotificationManager.IMPORTANCE_LOW
+ )
+ )
+ nm.createNotificationChannel(
+ NotificationChannel(
+ Constants.Strings.CHANNEL_NOTI_SCR_ERR,
+ getString(R.string.channel_err), NotificationManager.IMPORTANCE_MIN
+ )
+ )
+ nm.createNotificationChannel(
+ NotificationChannel(
+ Constants.Strings.CHANNEL_NOTI_NEW_APP,
+ getString(R.string.new_player, getString(R.string.new_app)),
+ NotificationManager.IMPORTANCE_LOW
+ )
+ )
+ nm.createNotificationChannel(
+ NotificationChannel(
+ Constants.Strings.CHANNEL_NOTI_PENDING,
+ getString(R.string.pending_scrobbles), NotificationManager.IMPORTANCE_MIN
+ )
+ )
+ }
+
+ private fun enableStrictMode() {
+ StrictMode.setThreadPolicy(
+ StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyLog()
+ .penaltyFlashScreen()
+ .build()
+ )
+ StrictMode.setVmPolicy(
+ StrictMode.VmPolicy.Builder()
+ .detectActivityLeaks()
+ .detectFileUriExposure()
+ .detectLeakedClosableObjects()
+ .detectLeakedRegistrationObjects()
+ .detectLeakedSqlLiteObjects()
+ .penaltyLog()
+ .build()
+ )
+ }
+
override fun getWorkManagerConfiguration(): Configuration =
Configuration.Builder()
.setWorkerFactory(workerFactory)
diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/dashboard/DashboardActivity.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/dashboard/DashboardActivity.kt
index cd2b44fe..d9c9e4e5 100644
--- a/app/src/main/java/org/listenbrainz/android/ui/screens/dashboard/DashboardActivity.kt
+++ b/app/src/main/java/org/listenbrainz/android/ui/screens/dashboard/DashboardActivity.kt
@@ -1,5 +1,7 @@
package org.listenbrainz.android.ui.screens.dashboard
+import android.app.ActivityManager
+import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -26,6 +28,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.listenbrainz.android.application.App
import org.listenbrainz.android.model.PermissionStatus
+import org.listenbrainz.android.service.ListenScrobbleService
import org.listenbrainz.android.ui.components.DialogLB
import org.listenbrainz.android.ui.navigation.AppNavigation
import org.listenbrainz.android.ui.navigation.BottomNavigationBar
@@ -194,9 +197,21 @@ class DashboardActivity : ComponentActivity() {
override fun onResume() {
super.onResume()
lifecycleScope.launch(Dispatchers.Main) {
- if(dashBoardViewModel.isNotificationListenerServiceAllowed()) {
- App.startListenService()
+ if (dashBoardViewModel.isNotificationListenerServiceAllowed()) {
+ if (!isServiceRunning(ListenScrobbleService::class.java)) {
+ App.startListenService()
+ }
+ }
+ }
+ }
+
+ private fun isServiceRunning(serviceClass: Class<*>): Boolean {
+ val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (serviceClass.name == service.service.className) {
+ return true
}
}
+ return false
}
}
diff --git a/app/src/main/java/org/listenbrainz/android/util/Constants.kt b/app/src/main/java/org/listenbrainz/android/util/Constants.kt
index f072425a..966a6528 100644
--- a/app/src/main/java/org/listenbrainz/android/util/Constants.kt
+++ b/app/src/main/java/org/listenbrainz/android/util/Constants.kt
@@ -14,10 +14,6 @@ object Constants {
const val LISTENBRAINZ_API_BASE_URL = "https://api.listenbrainz.org/1/"
const val ONBOARDING = "onboarding_lb"
const val ABOUT_URL = "https://listenbrainz.org/about"
-
- object Headers {
- const val AUTHORIZATION = "Authorization"
- }
object Strings {
const val TIMESTAMP = "timestamp"
@@ -35,6 +31,11 @@ object Constants {
const val REFRESH_TOKEN = "refresh_token"
const val STATUS_LOGGED_IN = 1
const val STATUS_LOGGED_OUT = 0
+
+ const val CHANNEL_NOTI_SCROBBLING = "noti_scrobbling"
+ const val CHANNEL_NOTI_SCR_ERR = "noti_scrobble_errors"
+ const val CHANNEL_NOTI_NEW_APP = "noti_new_app"
+ const val CHANNEL_NOTI_PENDING = "noti_pending_scrobbles"
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d695347f..5577fea4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -230,6 +230,34 @@
tt_yim_statistics_parent
SettingsActivity
+
+ Now scrobbling…
+ Scrobbled
+ Unscrobble
+ Unscrobbled
+ Unrecognised artist
+ (Tap to edit)
+ Recently Scrobbled
+ %1$s detected
+ Keep scrobbling this app?
+ No songs here yet…
+ Scrobble errors
+ New app
+ No apps are enabled
+ %1$d more
+ Pending Scrobbles
+ Pending scrobble
+ Processing pending scrobbles
+ %1$s top scrobbles
+
You acknowledge and agree that your contributed reviews to CritiqueBrainz are licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) license. You agree to license your work under this license. You represent and warrant that you own or control all rights in and to the work, that nothing in the work infringes the rights of any third-party, and that you have the permission to use and to license the work under the selected Creative Commons license. Finally, you give the MetaBrainz Foundation permission to license this content for commercial use outside of Creative Commons licenses in order to support the operations of the organization.
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 5077ae61..b409601e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,10 +3,10 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
ext {
kotlin_version = '1.9.10'
- navigationVersion = '2.7.3'
- hilt_version = '2.48'
- compose_version = '1.5.2'
- room_version = '2.5.2'
+ navigationVersion = '2.7.5'
+ hilt_version = '2.48.1'
+ compose_version = '1.5.4'
+ room_version = '2.6.0'
accompanist_version = '0.32.0'
work_version = "2.8.1"
exoplayer_version = '2.19.1'
diff --git a/sharedTest/build.gradle b/sharedTest/build.gradle
index eb629d12..e238eedd 100644
--- a/sharedTest/build.gradle
+++ b/sharedTest/build.gradle
@@ -30,7 +30,7 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.9.0'
+ implementation 'com.google.android.material:material:1.10.0'
//Web Service Setup
implementation 'com.google.code.gson:gson:2.10.1'
@@ -43,7 +43,7 @@ dependencies {
implementation 'com.squareup.okhttp3:mockwebserver:5.0.0-alpha.11'
implementation 'androidx.arch.core:core-testing:2.2.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
- implementation "androidx.room:room-testing:2.5.2"
+ implementation 'androidx.room:room-testing:2.6.0'
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
implementation 'androidx.test:runner:1.5.2'
From 3917339291b96741b0b9a3cfa0d6f6249288263d Mon Sep 17 00:00:00 2001
From: Akshat Tiwari <51470769+akshaaatt@users.noreply.github.com>
Date: Sun, 12 Nov 2023 18:21:13 +0530
Subject: [PATCH 2/5] Cleanup code
---
.../android/service/ListenScrobbleService.kt | 46 +++++++---
.../android/service/ListenSubmissionWorker.kt | 81 ++++++++++--------
.../ui/screens/dashboard/DashboardActivity.kt | 13 +--
.../listenbrainz/android/util/Constants.kt | 1 +
.../android/util/ListenSessionListener.kt | 9 ++
.../android/util/ListenSubmissionState.kt | 1 -
.../org/listenbrainz/android/util/Utils.kt | 83 +++++++++++++++++++
build.gradle | 2 +-
8 files changed, 173 insertions(+), 63 deletions(-)
diff --git a/app/src/main/java/org/listenbrainz/android/service/ListenScrobbleService.kt b/app/src/main/java/org/listenbrainz/android/service/ListenScrobbleService.kt
index 0fd1d624..d294e42a 100644
--- a/app/src/main/java/org/listenbrainz/android/service/ListenScrobbleService.kt
+++ b/app/src/main/java/org/listenbrainz/android/service/ListenScrobbleService.kt
@@ -1,11 +1,15 @@
package org.listenbrainz.android.service
+import android.app.NotificationChannel
+import android.app.NotificationManager
import android.app.Service
import android.content.ComponentName
import android.content.Intent
import android.media.session.MediaSessionManager
+import android.os.Build
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
+import androidx.core.content.ContextCompat
import androidx.work.WorkManager
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
@@ -13,8 +17,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.listenbrainz.android.repository.listens.ListensRepository
import org.listenbrainz.android.repository.preferences.AppPreferences
+import org.listenbrainz.android.util.Constants.Strings.CHANNEL_ID
import org.listenbrainz.android.util.ListenSessionListener
import org.listenbrainz.android.util.Log.d
+import org.listenbrainz.android.util.Log.e
import javax.inject.Inject
@AndroidEntryPoint
@@ -33,6 +39,12 @@ class ListenScrobbleService : NotificationListenerService() {
private var sessionListener: ListenSessionListener? = null
private var listenServiceComponent: ComponentName? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+ private val nm by lazy {
+ ContextCompat.getSystemService(
+ this,
+ NotificationManager::class.java
+ )!!
+ }
override fun onCreate() {
super.onCreate()
@@ -46,22 +58,15 @@ class ListenScrobbleService : NotificationListenerService() {
sessionManager = applicationContext.getSystemService(MEDIA_SESSION_SERVICE) as MediaSessionManager
sessionListener = ListenSessionListener(appPreferences, workManager, scope)
listenServiceComponent = ComponentName(this, this.javaClass)
-
+ createNotificationChannel()
+
try {
- sessionManager?.addOnActiveSessionsChangedListener(
- sessionListener!!,
- listenServiceComponent
- )
+ sessionManager?.addOnActiveSessionsChangedListener(sessionListener!!, listenServiceComponent)
+ } catch (e: SecurityException) {
+ e("Could not add session listener due to security exception: ${e.message}")
} catch (e: Exception) {
- e.printStackTrace()
- // Remove orphan entries.
- try {
- sessionManager?.removeOnActiveSessionsChangedListener(sessionListener!!)
- } catch (e: Exception) {
- e.printStackTrace()
- }
+ e("Could not add session listener: ${e.message}")
}
-
}
override fun onDestroy() {
@@ -78,4 +83,19 @@ class ListenScrobbleService : NotificationListenerService() {
override fun onNotificationRemoved(sbn: StatusBarNotification) {
super.onNotificationRemoved(sbn)
}
+
+ companion object {
+ private const val CHANNEL_NAME = "Scrobbling"
+ private const val CHANNEL_DESCRIPTION = "Shows notifications when a song is played"
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val importance = NotificationManager.IMPORTANCE_LOW
+ val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance).apply {
+ description = CHANNEL_DESCRIPTION
+ }
+ nm.createNotificationChannel(channel)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/listenbrainz/android/service/ListenSubmissionWorker.kt b/app/src/main/java/org/listenbrainz/android/service/ListenSubmissionWorker.kt
index 43db9391..2ef01629 100644
--- a/app/src/main/java/org/listenbrainz/android/service/ListenSubmissionWorker.kt
+++ b/app/src/main/java/org/listenbrainz/android/service/ListenSubmissionWorker.kt
@@ -56,7 +56,10 @@ class ListenSubmissionWorker @AssistedInject constructor(
// Our listen to submit
val listen = ListenSubmitBody.Payload(
- timestamp = if(inputData.getString("TYPE") == ListenType.SINGLE.code) inputData.getLong(Constants.Strings.TIMESTAMP, 0) else null,
+ timestamp = when (ListenType.SINGLE.code) {
+ inputData.getString("TYPE") -> inputData.getLong(Constants.Strings.TIMESTAMP, 0)
+ else -> null
+ },
metadata = metadata
)
@@ -69,45 +72,51 @@ class ListenSubmissionWorker @AssistedInject constructor(
repository.submitListen(token, body)
}
- return if (response.status == Resource.Status.SUCCESS){
- d("Listen submitted.")
-
- // Means conditions are met. Work manager automatically manages internet state.
- val pendingListens = pendingListensDao.getPendingListens()
-
- if (pendingListens.isNotEmpty()) {
-
- val submission = withContext(Dispatchers.IO){
- repository.submitListen(
- token,
- ListenSubmitBody().apply {
- listenType = "import"
- addListens(listensList = pendingListens)
+ return when (response.status) {
+ Resource.Status.SUCCESS -> {
+ d("Listen submitted.")
+
+ // Means conditions are met. Work manager automatically manages internet state.
+ val pendingListens = pendingListensDao.getPendingListens()
+
+ if (pendingListens.isNotEmpty()) {
+
+ val submission = withContext(Dispatchers.IO){
+ repository.submitListen(
+ token,
+ ListenSubmitBody().apply {
+ listenType = "import"
+ addListens(listensList = pendingListens)
+ }
+
+ )
+ }
+
+ when (submission.status) {
+ Resource.Status.SUCCESS -> {
+ // Empty all pending listens.
+ d("Pending listens submitted.")
+ pendingListensDao.deleteAllPendingListens()
}
-
- )
- }
-
- if (submission.status == Resource.Status.SUCCESS){
- // Empty all pending listens.
- d("Pending listens submitted.")
- pendingListensDao.deleteAllPendingListens()
- } else {
- w("Could not submit pending listens.")
+ else -> {
+ w("Could not submit pending listens.")
+ }
+ }
}
+
+ Result.success()
+
}
-
- Result.success()
-
- } else {
- // In case of failure, we add this listen to pending list.
- if (inputData.getString("TYPE") == "single"){
- // We don't want to submit playing nows later.
- d("Submission failed, listen saved.")
- pendingListensDao.addListen(listen)
+ else -> {
+ // In case of failure, we add this listen to pending list.
+ if (inputData.getString("TYPE") == "single"){
+ // We don't want to submit playing nows later.
+ d("Submission failed, listen saved.")
+ pendingListensDao.addListen(listen)
+ }
+
+ Result.failure()
}
-
- Result.failure()
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/dashboard/DashboardActivity.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/dashboard/DashboardActivity.kt
index d9c9e4e5..b282a5f2 100644
--- a/app/src/main/java/org/listenbrainz/android/ui/screens/dashboard/DashboardActivity.kt
+++ b/app/src/main/java/org/listenbrainz/android/ui/screens/dashboard/DashboardActivity.kt
@@ -1,7 +1,5 @@
package org.listenbrainz.android.ui.screens.dashboard
-import android.app.ActivityManager
-import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -37,6 +35,7 @@ import org.listenbrainz.android.ui.screens.brainzplayer.BrainzPlayerBackDropScre
import org.listenbrainz.android.ui.screens.search.SearchScreen
import org.listenbrainz.android.ui.screens.search.rememberSearchBarState
import org.listenbrainz.android.ui.theme.ListenBrainzTheme
+import org.listenbrainz.android.util.Utils.isServiceRunning
import org.listenbrainz.android.viewmodel.DashBoardViewModel
@AndroidEntryPoint
@@ -204,14 +203,4 @@ class DashboardActivity : ComponentActivity() {
}
}
}
-
- private fun isServiceRunning(serviceClass: Class<*>): Boolean {
- val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
- for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
- if (serviceClass.name == service.service.className) {
- return true
- }
- }
- return false
- }
}
diff --git a/app/src/main/java/org/listenbrainz/android/util/Constants.kt b/app/src/main/java/org/listenbrainz/android/util/Constants.kt
index 966a6528..75d17a88 100644
--- a/app/src/main/java/org/listenbrainz/android/util/Constants.kt
+++ b/app/src/main/java/org/listenbrainz/android/util/Constants.kt
@@ -36,6 +36,7 @@ object Constants {
const val CHANNEL_NOTI_SCR_ERR = "noti_scrobble_errors"
const val CHANNEL_NOTI_NEW_APP = "noti_new_app"
const val CHANNEL_NOTI_PENDING = "noti_pending_scrobbles"
+ const val CHANNEL_ID = "listen_scrobble_channel"
}
}
diff --git a/app/src/main/java/org/listenbrainz/android/util/ListenSessionListener.kt b/app/src/main/java/org/listenbrainz/android/util/ListenSessionListener.kt
index e98d427f..970b178a 100644
--- a/app/src/main/java/org/listenbrainz/android/util/ListenSessionListener.kt
+++ b/app/src/main/java/org/listenbrainz/android/util/ListenSessionListener.kt
@@ -15,9 +15,11 @@ import org.listenbrainz.android.util.Log.d
class ListenSessionListener(val appPreferences: AppPreferences, val workManager: WorkManager, private val serviceScope: CoroutineScope) : OnActiveSessionsChangedListener {
private val activeSessions: MutableMap = HashMap()
+ private var controllers: List? = null
@Synchronized
override fun onActiveSessionsChanged(controllers: List?) {
+ this.controllers = controllers
d("onActiveSessionsChanged: EXECUTED")
if (controllers == null) return
clearSessions()
@@ -60,6 +62,13 @@ class ListenSessionListener(val appPreferences: AppPreferences, val workManager:
activeSessions.clear()
}
+ fun isMediaPlaying() =
+ controllers?.any {
+ it.playbackState?.state == PlaybackState.STATE_PLAYING &&
+ !it.metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST).isNullOrEmpty() &&
+ !it.metadata?.getString(MediaMetadata.METADATA_KEY_TITLE).isNullOrEmpty()
+ } ?: false
+
private inner class ListenCallback(private val player: String) : MediaController.Callback() {
val listenSubmissionState: ListenSubmissionState = ListenSubmissionState()
diff --git a/app/src/main/java/org/listenbrainz/android/util/ListenSubmissionState.kt b/app/src/main/java/org/listenbrainz/android/util/ListenSubmissionState.kt
index 960c55a4..e095713b 100644
--- a/app/src/main/java/org/listenbrainz/android/util/ListenSubmissionState.kt
+++ b/app/src/main/java/org/listenbrainz/android/util/ListenSubmissionState.kt
@@ -11,7 +11,6 @@ import com.dariobrux.kotimer.interfaces.OnTimerListener
import org.listenbrainz.android.model.ListenType
import org.listenbrainz.android.service.ListenSubmissionWorker
-
class ListenSubmissionState(
private var artist: String? = null,
private var title: String? = null,
diff --git a/app/src/main/java/org/listenbrainz/android/util/Utils.kt b/app/src/main/java/org/listenbrainz/android/util/Utils.kt
index febc65ab..185116d2 100644
--- a/app/src/main/java/org/listenbrainz/android/util/Utils.kt
+++ b/app/src/main/java/org/listenbrainz/android/util/Utils.kt
@@ -1,5 +1,7 @@
package org.listenbrainz.android.util
+import android.app.ActivityManager
+import android.app.NotificationManager
import android.content.ActivityNotFoundException
import android.content.ContentValues
import android.content.Context
@@ -7,6 +9,9 @@ import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Icon
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
@@ -15,12 +20,16 @@ import android.provider.MediaStore
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
+import androidx.annotation.RequiresApi
import androidx.annotation.WorkerThread
+import androidx.core.app.NotificationCompat
import kotlinx.coroutines.Dispatchers
import okhttp3.*
import org.listenbrainz.android.R
import org.listenbrainz.android.model.ResponseError
import org.listenbrainz.android.model.ResponseError.Companion.getError
+import org.listenbrainz.android.service.ListenScrobbleService
+import org.listenbrainz.android.util.Constants.Strings.CHANNEL_ID
import org.listenbrainz.android.util.Log.e
import retrofit2.Response
import java.io.*
@@ -125,6 +134,80 @@ object Utils {
return null
}
+ fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
+ val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (serviceClass.name == service.service.className) {
+ return true
+ }
+ }
+ return false
+ }
+
+ fun notifyScrobble(songTitle: String, artistName: String, albumArt: Bitmap?, nm: NotificationManager, context: Context) {
+ val notificationBuilder = NotificationCompat.Builder(context,
+ CHANNEL_ID
+ )
+ .setContentTitle(songTitle)
+ .setContentText(artistName)
+ .setSmallIcon(R.drawable.ic_listenbrainz_logo_no_text)
+ .setLargeIcon(albumArt) // Set the album art here
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setAutoCancel(false)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+
+ try {
+ nm.notify(0, notificationBuilder.build())
+ } catch (e: RuntimeException) {
+ e("Error showing notification")
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ private fun Icon.toBitmap(context: Context): Bitmap? {
+ val drawable = this.loadDrawable(context)
+ return if (drawable is BitmapDrawable) {
+ drawable.bitmap
+ } else {
+ val bitmap = drawable?.let {
+ Bitmap.createBitmap(
+ it.intrinsicWidth,
+ drawable.intrinsicHeight,
+ Bitmap.Config.ARGB_8888
+ )
+ }
+ val canvas = bitmap?.let { Canvas(it) }
+ if (canvas != null) {
+ drawable.setBounds(0, 0, canvas.width, canvas.height)
+ drawable.draw(canvas)
+ }
+ bitmap
+ }
+ }
+
+ fun scrobbleFromNotiExtractMeta(titleStr: String, formatStr: String): Pair? {
+ val tpos = formatStr.indexOf("%1\$s")
+ val apos = formatStr.indexOf("%2\$s")
+ val regex = formatStr.replace("(", "\\(")
+ .replace(")", "\\)")
+ .replace("%1\$s", "(.*)")
+ .replace("%2\$s", "(.*)")
+ return try {
+ val m = regex.toRegex().find(titleStr)!!
+ val g = m.groupValues
+ if (g.size != 3)
+ throw IllegalArgumentException("group size != 3")
+ if (tpos > apos)
+ g[1] to g[2]
+ else
+ g[2] to g[1]
+
+ } catch (e: Exception) {
+ print("err in $titleStr $formatStr")
+ null
+ }
+ }
+
fun stringFromAsset(context: Context, asset: String?): String {
return try {
val input = context.resources.assets.open(asset!!)
diff --git a/build.gradle b/build.gradle
index b409601e..c58a74d5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,7 +17,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.1.2'
+ classpath 'com.android.tools.build:gradle:8.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
From 955c93350bccc796f97ccccce2063c055572d758 Mon Sep 17 00:00:00 2001
From: Akshat Tiwari <51470769+akshaaatt@users.noreply.github.com>
Date: Mon, 13 Nov 2023 02:46:10 +0530
Subject: [PATCH 3/5] Add logger
---
app/build.gradle | 1 +
app/src/main/AndroidManifest.xml | 11 ++++
.../listenbrainz/android/application/App.kt | 22 ++++++++
.../ui/screens/settings/SettingsScreen.kt | 54 +++++++++++++++++++
.../java/org/listenbrainz/android/util/Log.kt | 14 ++---
app/src/main/res/xml/provider_paths.xml | 6 +++
6 files changed, 98 insertions(+), 10 deletions(-)
create mode 100644 app/src/main/res/xml/provider_paths.xml
diff --git a/app/build.gradle b/app/build.gradle
index 5e42ec83..1ebcbd0f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -250,4 +250,5 @@ dependencies {
// Third party libraries
implementation 'com.github.dariobrux:Timer:1.1.0'
implementation 'com.github.a914-gowtham:compose-ratingbar:1.3.4'
+ implementation 'com.github.akshaaatt:Logger-Android:1.0.0'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 63b5efca..f674c569 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -107,6 +107,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/listenbrainz/android/application/App.kt b/app/src/main/java/org/listenbrainz/android/application/App.kt
index a92c7678..671a0b16 100644
--- a/app/src/main/java/org/listenbrainz/android/application/App.kt
+++ b/app/src/main/java/org/listenbrainz/android/application/App.kt
@@ -11,6 +11,8 @@ import androidx.hilt.work.HiltWorkerFactory
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration
+import com.limurse.logger.Logger
+import com.limurse.logger.config.Config
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
@@ -32,6 +34,15 @@ class App : Application(), Configuration.Provider {
override fun onCreate() {
super.onCreate()
+ val logDirectory = applicationContext.getExternalFilesDir(null)?.path.orEmpty()
+ val config = Config.Builder(logDirectory)
+ .setDefaultTag(Constants.TAG)
+ .setLogcatEnable(true)
+ .setDataFormatterPattern("dd-MM-yyyy-HH:mm:ss")
+ .setStartupData(collectStartupData())
+ .build()
+
+ Logger.init(config)
if (BuildConfig.DEBUG) {
enableStrictMode()
@@ -52,6 +63,17 @@ class App : Application(), Configuration.Provider {
createChannels()
}
+ private fun collectStartupData(): Map = mapOf(
+ "App Version" to System.currentTimeMillis().toString(),
+ "Device Application Id" to BuildConfig.APPLICATION_ID,
+ "Device Version Code" to BuildConfig.VERSION_CODE.toString(),
+ "Device Version Name" to BuildConfig.VERSION_NAME,
+ "Device Build Type" to BuildConfig.BUILD_TYPE,
+ "Device" to Build.DEVICE,
+ "Device SDK" to Build.VERSION.SDK_INT.toString(),
+ "Device Manufacturer" to Build.MANUFACTURER
+ )
+
private fun createChannels() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return
diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
index 0d167e8f..0d3347a4 100644
--- a/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
@@ -46,7 +46,10 @@ import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
+import com.limurse.logger.Logger
+import com.limurse.logger.util.FileIntent
import kotlinx.coroutines.launch
+import org.listenbrainz.android.BuildConfig
import org.listenbrainz.android.R
import org.listenbrainz.android.model.UiMode
import org.listenbrainz.android.ui.components.Switch
@@ -264,6 +267,57 @@ fun SettingsScreen(
Divider(thickness = 1.dp)
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(18.dp)
+ .clickable {
+ Logger.apply {
+ compressLogsInZipFile { zipFile ->
+ zipFile?.let {
+ FileIntent.fromFile(
+ context,
+ it,
+ BuildConfig.APPLICATION_ID
+ )?.let { intent ->
+ intent.putExtra(Intent.EXTRA_SUBJECT, "Log File")
+ try {
+ context.startActivity(Intent.createChooser(intent, "Email logs..."))
+ } catch (e: java.lang.Exception) {
+ e(throwable = e)
+ }
+ }
+ }
+ }
+ }
+ }
+ ,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column {
+ Text(
+ text = "Report an issue",
+ color = when {
+ viewModel.appPreferences.isNotificationServiceAllowed -> MaterialTheme.colorScheme.onSurface
+ else -> Color(0xFF949494)
+ }
+ )
+
+ Text(
+ text = "Submit app logs for further investigation",
+ lineHeight = 18.sp,
+ fontSize = 12.sp,
+ color = Color(0xFF949494),
+ modifier = Modifier
+ .padding(top = 6.dp)
+ .width(240.dp)
+ )
+ }
+ }
+
+ Divider(thickness = 1.dp)
+
Row(
modifier = Modifier
.fillMaxWidth()
diff --git a/app/src/main/java/org/listenbrainz/android/util/Log.kt b/app/src/main/java/org/listenbrainz/android/util/Log.kt
index c87c940a..1878ff21 100644
--- a/app/src/main/java/org/listenbrainz/android/util/Log.kt
+++ b/app/src/main/java/org/listenbrainz/android/util/Log.kt
@@ -1,24 +1,18 @@
package org.listenbrainz.android.util
-import android.util.Log
+import com.limurse.logger.Logger
object Log {
- private const val TAG = Constants.TAG
-
fun e(message: String) {
- Log.e(TAG, message)
+ Logger.e(msg = message)
}
fun d(message: String) {
- Log.d(TAG, message)
- }
-
- fun v(message: String) {
- Log.v(TAG, message)
+ Logger.d(msg = message)
}
fun w(message: String) {
- Log.w(TAG, message)
+ Logger.w(msg = message)
}
}
\ No newline at end of file
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 00000000..0106c4d1
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
From 8540f0cb0fe0dff855e8b56d5e16425ea881d92a Mon Sep 17 00:00:00 2001
From: Akshat Tiwari <51470769+akshaaatt@users.noreply.github.com>
Date: Mon, 13 Nov 2023 03:00:43 +0530
Subject: [PATCH 4/5] Fix email template
---
.../android/ui/screens/settings/SettingsScreen.kt | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
index 0d3347a4..7a09c9ba 100644
--- a/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
@@ -43,6 +43,7 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.core.content.FileProvider
import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
@@ -280,7 +281,11 @@ fun SettingsScreen(
it,
BuildConfig.APPLICATION_ID
)?.let { intent ->
- intent.putExtra(Intent.EXTRA_SUBJECT, "Log File")
+ intent.putExtra(Intent.EXTRA_SUBJECT, "Log Files")
+ intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("android@metabrainz.org"))
+ intent.putExtra(Intent.EXTRA_TEXT, "Please find the attached log files.")
+ intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", zipFile))
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
context.startActivity(Intent.createChooser(intent, "Email logs..."))
} catch (e: java.lang.Exception) {
From 0f37a3bc5cc7584aaf9588846da3dfa6611ae8dc Mon Sep 17 00:00:00 2001
From: Akshat Tiwari <51470769+akshaaatt@users.noreply.github.com>
Date: Mon, 13 Nov 2023 22:55:11 +0530
Subject: [PATCH 5/5] Update email
---
.../listenbrainz/android/ui/screens/settings/SettingsScreen.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
index 7a09c9ba..9c5cfcdb 100644
--- a/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/org/listenbrainz/android/ui/screens/settings/SettingsScreen.kt
@@ -282,7 +282,7 @@ fun SettingsScreen(
BuildConfig.APPLICATION_ID
)?.let { intent ->
intent.putExtra(Intent.EXTRA_SUBJECT, "Log Files")
- intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("android@metabrainz.org"))
+ intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("mobile@metabrainz.org"))
intent.putExtra(Intent.EXTRA_TEXT, "Please find the attached log files.")
intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", zipFile))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)