From 6ed091ecc4cbbc09c7b2cce73ff48cc70c3871a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:30:59 +0000 Subject: [PATCH 1/5] Fix app crash when Google Play Services not ready after car OTA update Co-authored-by: chamika <754909+chamika@users.noreply.github.com> Agent-Logs-Url: https://github.com/chamika/DashTune/sessions/444d2f81-f987-4ad3-a703-c076d24c221c --- .../chamika/dashtune/DashTuneApplication.kt | 18 ++++++++++--- .../chamika/dashtune/DashTuneMusicService.kt | 14 +++++++++- .../dashtune/DashTuneSessionCallback.kt | 26 ++++++++++++++----- .../dashtune/signin/SignInViewModel.kt | 16 ++++++++++-- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneApplication.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneApplication.kt index 03afaeb..4108289 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneApplication.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneApplication.kt @@ -1,6 +1,8 @@ package com.chamika.dashtune import android.app.Application +import android.util.Log +import com.chamika.dashtune.Constants.LOG_TAG import com.google.firebase.Firebase import com.google.firebase.analytics.analytics import com.google.firebase.crashlytics.crashlytics @@ -11,10 +13,18 @@ class DashTuneApplication : Application() { override fun onCreate() { super.onCreate() - // Disable Firebase collection in debug builds - if (BuildConfig.DEBUG) { - Firebase.analytics.setAnalyticsCollectionEnabled(false) - Firebase.crashlytics.setCrashlyticsCollectionEnabled(false) + // Disable Firebase collection in debug builds. + // Wrapped in try-catch because Firebase depends on Google Play Services, which may not + // yet be ready shortly after a car software update (GMS can take several minutes to + // initialise on first boot after an OTA). Crashing here would prevent the media service + // from starting and cause the AAOS "Something went wrong" error dialog. + try { + if (BuildConfig.DEBUG) { + Firebase.analytics.setAnalyticsCollectionEnabled(false) + Firebase.crashlytics.setCrashlyticsCollectionEnabled(false) + } + } catch (e: Exception) { + Log.w(LOG_TAG, "Firebase init skipped – Google Play Services not ready yet", e) } } } diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt index db0727e..e8479ea 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt @@ -364,7 +364,7 @@ class DashTuneMusicService : MediaLibraryService() { downloadManager.resumeDownloads() } catch (e: Exception) { Log.w(LOG_TAG, "Failed to prefetch: $id", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) } } } @@ -404,6 +404,18 @@ class DashTuneMusicService : MediaLibraryService() { } } + /** + * Records [e] to Firebase Crashlytics, swallowing any exception that might occur if Google + * Play Services is not yet available (e.g. shortly after a car OTA update). + */ + private fun safeRecordException(e: Exception) { + try { + FirebaseCrashlytics.getInstance().recordException(e) + } catch (crashlyticsError: Exception) { + Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", crashlyticsError) + } + } + private val downloadListener = object : DownloadManager.Listener { override fun onDownloadChanged( downloadManager: DownloadManager, diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt index ee97417..39c33ed 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt @@ -115,7 +115,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get library root", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "")) } } @@ -148,7 +148,7 @@ class DashTuneSessionCallback( LibraryResult.ofItemList(repository.getChildren(parentId), params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get children for $parentId", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "Failed to get media items")) } } @@ -190,7 +190,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get item $mediaId", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "Failed to get media item")) } } @@ -289,7 +289,7 @@ class DashTuneSessionCallback( LibraryResult.ofVoid(params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to search for '$query'", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) LibraryResult.ofVoid() } } @@ -309,7 +309,7 @@ class DashTuneSessionCallback( LibraryResult.ofItemList(results, params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get search results for '$query'", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) LibraryResult.ofItemList(emptyList(), params) } } @@ -343,7 +343,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to resume playback", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) MediaSession.MediaItemsWithStartPosition( emptyList(), 0, @@ -418,7 +418,7 @@ class DashTuneSessionCallback( SessionResult(SessionResult.RESULT_SUCCESS) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to set rating for $mediaId", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) SessionResult(SessionError.ERROR_UNKNOWN) } } @@ -440,4 +440,16 @@ class DashTuneSessionCallback( jellyfinApi.userLibraryApi.unmarkFavoriteItem(id) } } + + /** + * Records [e] to Firebase Crashlytics, swallowing any exception that might occur if Google + * Play Services is not yet available (e.g. shortly after a car OTA update). + */ + private fun safeRecordException(e: Exception) { + try { + FirebaseCrashlytics.getInstance().recordException(e) + } catch (crashlyticsError: Exception) { + Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", crashlyticsError) + } + } } diff --git a/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt b/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt index 9a273c0..53add64 100644 --- a/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt +++ b/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt @@ -49,7 +49,7 @@ class SignInViewModel @Inject constructor() : ViewModel() { response.status == 200 } catch (e: Exception) { Log.w(LOG_TAG, "Error", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) false } } @@ -125,7 +125,7 @@ class SignInViewModel @Inject constructor() : ViewModel() { response.status == 200 } catch (e: Exception) { Log.e(LOG_TAG, "Error", e) - FirebaseCrashlytics.getInstance().recordException(e) + safeRecordException(e) false } } @@ -136,6 +136,18 @@ class SignInViewModel @Inject constructor() : ViewModel() { _loggedIn.postValue(true) } + /** + * Records [e] to Firebase Crashlytics, swallowing any exception that might occur if Google + * Play Services is not yet available (e.g. shortly after a car OTA update). + */ + private fun safeRecordException(e: Exception) { + try { + FirebaseCrashlytics.getInstance().recordException(e) + } catch (crashlyticsError: Exception) { + Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", crashlyticsError) + } + } + companion object { internal const val JELLYFIN_SERVER_URL = "jellyfinServer" } From 7d3584049ec8a55d4aa30b162c87627fa156d21e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:38:17 +0000 Subject: [PATCH 2/5] Refactor: extract safeRecordException into FirebaseUtils utility object Co-authored-by: chamika <754909+chamika@users.noreply.github.com> Agent-Logs-Url: https://github.com/chamika/DashTune/sessions/62d93e36-1aa4-4ae6-ba51-3d34d84e2cf3 --- .../chamika/dashtune/DashTuneMusicService.kt | 15 +---------- .../dashtune/DashTuneSessionCallback.kt | 27 +++++-------------- .../com/chamika/dashtune/FirebaseUtils.kt | 20 ++++++++++++++ .../dashtune/signin/SignInViewModel.kt | 18 +++---------- 4 files changed, 31 insertions(+), 49 deletions(-) create mode 100644 automotive/src/main/java/com/chamika/dashtune/FirebaseUtils.kt diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt index e8479ea..33fb1d2 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt @@ -33,7 +33,6 @@ import androidx.media3.session.MediaLibraryService import androidx.media3.session.MediaSession import androidx.preference.PreferenceManager import com.chamika.dashtune.Constants.LOG_TAG -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.chamika.dashtune.DashTuneSessionCallback.Companion.PLAYLIST_INDEX_PREF import com.chamika.dashtune.DashTuneSessionCallback.Companion.PLAYLIST_TRACK_POSITON_MS_PREF import com.chamika.dashtune.data.db.MediaCacheDao @@ -364,7 +363,7 @@ class DashTuneMusicService : MediaLibraryService() { downloadManager.resumeDownloads() } catch (e: Exception) { Log.w(LOG_TAG, "Failed to prefetch: $id", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) } } } @@ -404,18 +403,6 @@ class DashTuneMusicService : MediaLibraryService() { } } - /** - * Records [e] to Firebase Crashlytics, swallowing any exception that might occur if Google - * Play Services is not yet available (e.g. shortly after a car OTA update). - */ - private fun safeRecordException(e: Exception) { - try { - FirebaseCrashlytics.getInstance().recordException(e) - } catch (crashlyticsError: Exception) { - Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", crashlyticsError) - } - } - private val downloadListener = object : DownloadManager.Listener { override fun onDownloadChanged( downloadManager: DownloadManager, diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt index 39c33ed..8eb302a 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt @@ -25,7 +25,6 @@ import androidx.media3.session.SessionError import androidx.media3.session.SessionResult import androidx.preference.PreferenceManager import com.chamika.dashtune.Constants.LOG_TAG -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.chamika.dashtune.auth.JellyfinAccountManager import com.chamika.dashtune.data.MediaRepository import com.chamika.dashtune.data.db.MediaCacheDao @@ -115,7 +114,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get library root", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "")) } } @@ -148,7 +147,7 @@ class DashTuneSessionCallback( LibraryResult.ofItemList(repository.getChildren(parentId), params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get children for $parentId", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "Failed to get media items")) } } @@ -190,7 +189,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get item $mediaId", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "Failed to get media item")) } } @@ -289,7 +288,7 @@ class DashTuneSessionCallback( LibraryResult.ofVoid(params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to search for '$query'", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) LibraryResult.ofVoid() } } @@ -309,7 +308,7 @@ class DashTuneSessionCallback( LibraryResult.ofItemList(results, params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get search results for '$query'", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) LibraryResult.ofItemList(emptyList(), params) } } @@ -343,7 +342,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to resume playback", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) MediaSession.MediaItemsWithStartPosition( emptyList(), 0, @@ -418,7 +417,7 @@ class DashTuneSessionCallback( SessionResult(SessionResult.RESULT_SUCCESS) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to set rating for $mediaId", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) SessionResult(SessionError.ERROR_UNKNOWN) } } @@ -440,16 +439,4 @@ class DashTuneSessionCallback( jellyfinApi.userLibraryApi.unmarkFavoriteItem(id) } } - - /** - * Records [e] to Firebase Crashlytics, swallowing any exception that might occur if Google - * Play Services is not yet available (e.g. shortly after a car OTA update). - */ - private fun safeRecordException(e: Exception) { - try { - FirebaseCrashlytics.getInstance().recordException(e) - } catch (crashlyticsError: Exception) { - Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", crashlyticsError) - } - } } diff --git a/automotive/src/main/java/com/chamika/dashtune/FirebaseUtils.kt b/automotive/src/main/java/com/chamika/dashtune/FirebaseUtils.kt new file mode 100644 index 0000000..8d9d800 --- /dev/null +++ b/automotive/src/main/java/com/chamika/dashtune/FirebaseUtils.kt @@ -0,0 +1,20 @@ +package com.chamika.dashtune + +import android.util.Log +import com.chamika.dashtune.Constants.LOG_TAG +import com.google.firebase.crashlytics.FirebaseCrashlytics + +object FirebaseUtils { + + /** + * Records [e] to Firebase Crashlytics, swallowing any exception that might occur if Google + * Play Services is not yet available (e.g. shortly after a car OTA update). + */ + fun safeRecordException(e: Exception) { + try { + FirebaseCrashlytics.getInstance().recordException(e) + } catch (crashlyticsError: Exception) { + Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", crashlyticsError) + } + } +} diff --git a/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt b/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt index 53add64..5e2e89e 100644 --- a/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt +++ b/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.chamika.dashtune.Constants.LOG_TAG -import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.chamika.dashtune.FirebaseUtils import com.chamika.dashtune.auth.JellyfinAccountManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -49,7 +49,7 @@ class SignInViewModel @Inject constructor() : ViewModel() { response.status == 200 } catch (e: Exception) { Log.w(LOG_TAG, "Error", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) false } } @@ -125,7 +125,7 @@ class SignInViewModel @Inject constructor() : ViewModel() { response.status == 200 } catch (e: Exception) { Log.e(LOG_TAG, "Error", e) - safeRecordException(e) + FirebaseUtils.safeRecordException(e) false } } @@ -136,18 +136,6 @@ class SignInViewModel @Inject constructor() : ViewModel() { _loggedIn.postValue(true) } - /** - * Records [e] to Firebase Crashlytics, swallowing any exception that might occur if Google - * Play Services is not yet available (e.g. shortly after a car OTA update). - */ - private fun safeRecordException(e: Exception) { - try { - FirebaseCrashlytics.getInstance().recordException(e) - } catch (crashlyticsError: Exception) { - Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", crashlyticsError) - } - } - companion object { internal const val JELLYFIN_SERVER_URL = "jellyfinServer" } From 55a1fdf449c0947c8ecf81d674b7b6db29414fb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 09:20:42 +0000 Subject: [PATCH 3/5] Merge origin/main: add Crashlytics custom keys/logs, keep FirebaseUtils for recordException Co-authored-by: chamika <754909+chamika@users.noreply.github.com> Agent-Logs-Url: https://github.com/chamika/DashTune/sessions/e8ccf17a-f1b2-48c9-bbd2-94b33498c1e7 --- automotive/build.gradle.kts | 4 ++-- .../dashtune/AlbumArtContentProvider.kt | 20 +++++++++++++------ .../chamika/dashtune/DashTuneMusicService.kt | 20 +++++++++++++++++-- .../dashtune/DashTuneSessionCallback.kt | 18 +++++++++++++++-- .../chamika/dashtune/data/MediaRepository.kt | 11 ++++++---- .../dashtune/signin/SignInViewModel.kt | 8 ++++++++ 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/automotive/build.gradle.kts b/automotive/build.gradle.kts index a26df93..fdc1bc1 100644 --- a/automotive/build.gradle.kts +++ b/automotive/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "com.chamika.dashtune" minSdk = 28 targetSdk = 36 - versionCode = 10 - versionName = "0.9.1" + versionCode = 12 + versionName = "1.0.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt b/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt index f28d495..ca5662b 100644 --- a/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt +++ b/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt @@ -8,6 +8,7 @@ import android.net.Uri import android.os.ParcelFileDescriptor import android.util.Log import com.chamika.dashtune.Constants.LOG_TAG +import com.google.firebase.crashlytics.FirebaseCrashlytics import okhttp3.OkHttpClient import okhttp3.Request import okio.buffer @@ -64,15 +65,20 @@ class AlbumArtContentProvider : ContentProvider() { return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) } - synchronized(inProgress) { + val existingLatch = synchronized(inProgress) { if (inProgress.contains(remoteUri)) { - Log.d(LOG_TAG, "Waiting for image download in separate thread... $remoteUri") - inProgress.get(remoteUri)?.await(15, TimeUnit.SECONDS) - Log.d(LOG_TAG, "... Available!") - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) + inProgress[remoteUri] + } else { + inProgress[remoteUri] = CountDownLatch(1) + null } + } - inProgress.put(remoteUri, CountDownLatch(1)) + if (existingLatch != null) { + Log.d(LOG_TAG, "Waiting for image download in separate thread... $remoteUri") + existingLatch.await(15, TimeUnit.SECONDS) + Log.d(LOG_TAG, "... Available!") + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) } val tmpFile = File.createTempFile("dashtune-albumart", ".png", context.cacheDir) @@ -95,6 +101,8 @@ class AlbumArtContentProvider : ContentProvider() { tmpFile.renameTo(file) } else { Log.w(LOG_TAG, "Failed to download $remoteUri: \n ${it.code} - ${it.body}") + FirebaseCrashlytics.getInstance().setCustomKey("album_art_path", remoteUri.path ?: "unknown") + FirebaseCrashlytics.getInstance().recordException(Exception("Album art download failed: HTTP ${it.code}")) } inProgress.get(remoteUri)?.countDown() diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt index 33fb1d2..a5d90f0 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt @@ -37,6 +37,7 @@ import com.chamika.dashtune.DashTuneSessionCallback.Companion.PLAYLIST_INDEX_PRE import com.chamika.dashtune.DashTuneSessionCallback.Companion.PLAYLIST_TRACK_POSITON_MS_PREF import com.chamika.dashtune.data.db.MediaCacheDao import com.chamika.dashtune.media.MediaItemFactory.Companion.ROOT_ID +import com.google.firebase.crashlytics.FirebaseCrashlytics import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -96,6 +97,7 @@ class DashTuneMusicService : MediaLibraryService() { super.onCreate() Log.i(LOG_TAG, "onCreate") + FirebaseCrashlytics.getInstance().log("Service created") accountManager = com.chamika.dashtune.auth.JellyfinAccountManager( AccountManager.get(applicationContext) @@ -152,9 +154,13 @@ class DashTuneMusicService : MediaLibraryService() { if (prefetchCount > 0) { prefetchNextTracks(player, prefetchCount) } + + FirebaseCrashlytics.getInstance().setCustomKey("current_track_id", player.currentMediaItem?.mediaId ?: "") + FirebaseCrashlytics.getInstance().setCustomKey("playlist_size", player.mediaItemCount) } if (events.contains(Player.EVENT_IS_PLAYING_CHANGED)) { + FirebaseCrashlytics.getInstance().setCustomKey("is_playing", player.isPlaying) if (player.isPlaying) { startPlaybackPoll() } else { @@ -163,12 +169,14 @@ class DashTuneMusicService : MediaLibraryService() { } if (events.contains(Player.EVENT_REPEAT_MODE_CHANGED)) { + FirebaseCrashlytics.getInstance().setCustomKey("repeat_mode", player.repeatMode) PreferenceManager.getDefaultSharedPreferences(this@DashTuneMusicService).edit { putInt("repeat_mode", player.repeatMode) } } if (events.contains(Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED)) { + FirebaseCrashlytics.getInstance().setCustomKey("shuffle_enabled", player.shuffleModeEnabled) PreferenceManager.getDefaultSharedPreferences(this@DashTuneMusicService).edit { putBoolean("shuffle_enabled", player.shuffleModeEnabled) } @@ -210,7 +218,7 @@ class DashTuneMusicService : MediaLibraryService() { onLogin() } - connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager val networkRequest = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build() @@ -218,13 +226,16 @@ class DashTuneMusicService : MediaLibraryService() { networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { Log.i(LOG_TAG, "Network available") + FirebaseCrashlytics.getInstance().log("Network available") if (!accountManager.isAuthenticated) return val prefs = PreferenceManager.getDefaultSharedPreferences(this@DashTuneMusicService) val lastSync = prefs.getLong("last_sync_timestamp", 0L) val sixHoursMs = 6 * 60 * 60 * 1000L + val syncNeeded = System.currentTimeMillis() - lastSync > sixHoursMs - if (System.currentTimeMillis() - lastSync > sixHoursMs) { + if (syncNeeded) { + FirebaseCrashlytics.getInstance().log("Network available: sync triggered") serviceScope.launch { val success = callback.sync() if (success) { @@ -237,6 +248,8 @@ class DashTuneMusicService : MediaLibraryService() { ).show() } } + } else { + FirebaseCrashlytics.getInstance().log("Network available: sync skipped (ran recently)") } } } @@ -271,6 +284,7 @@ class DashTuneMusicService : MediaLibraryService() { override fun onDestroy() { Log.i(LOG_TAG, "onDestroy") + FirebaseCrashlytics.getInstance().log("Service destroyed") mediaLibrarySession.release() mediaLibrarySession.player.removeListener(playerListener) @@ -311,6 +325,7 @@ class DashTuneMusicService : MediaLibraryService() { } fun onLogin() { + FirebaseCrashlytics.getInstance().log("Login applied to service") jellyfinApi.update( baseUrl = accountManager.server, accessToken = accountManager.token @@ -363,6 +378,7 @@ class DashTuneMusicService : MediaLibraryService() { downloadManager.resumeDownloads() } catch (e: Exception) { Log.w(LOG_TAG, "Failed to prefetch: $id", e) + FirebaseCrashlytics.getInstance().setCustomKey("prefetch_track_id", id) FirebaseUtils.safeRecordException(e) } } diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt index 8eb302a..a02ee70 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt @@ -36,6 +36,7 @@ import com.chamika.dashtune.signin.SignInActivity import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture +import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import org.jellyfin.sdk.api.client.ApiClient @@ -92,7 +93,7 @@ class DashTuneSessionCallback( val itemFactory = MediaItemFactory(service, jellyfinApi, artSize) val tree = JellyfinMediaTree(service, jellyfinApi, itemFactory) - repository = MediaRepository(service, mediaCacheDao, tree, itemFactory) + repository = MediaRepository(mediaCacheDao, tree, itemFactory) } } @@ -114,6 +115,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get library root", e) + FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "get_library_root") FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "")) } @@ -147,6 +149,8 @@ class DashTuneSessionCallback( LibraryResult.ofItemList(repository.getChildren(parentId), params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get children for $parentId", e) + FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "get_children") + FirebaseCrashlytics.getInstance().setCustomKey("parent_id", parentId) FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "Failed to get media items")) } @@ -189,6 +193,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get item $mediaId", e) + FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "get_item") FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "Failed to get media item")) } @@ -288,6 +293,8 @@ class DashTuneSessionCallback( LibraryResult.ofVoid(params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to search for '$query'", e) + FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "search") + FirebaseCrashlytics.getInstance().setCustomKey("search_query", query) FirebaseUtils.safeRecordException(e) LibraryResult.ofVoid() } @@ -308,6 +315,8 @@ class DashTuneSessionCallback( LibraryResult.ofItemList(results, params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get search results for '$query'", e) + FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "get_search_result") + FirebaseCrashlytics.getInstance().setCustomKey("search_query", query) FirebaseUtils.safeRecordException(e) LibraryResult.ofItemList(emptyList(), params) } @@ -334,6 +343,7 @@ class DashTuneSessionCallback( ?.awaitAll() ?: listOf() Log.d(LOG_TAG, "Resuming playback with $mediaItemsToRestore") + FirebaseCrashlytics.getInstance().log("Restoring playback: index=${prefs.getInt(PLAYLIST_INDEX_PREF, 0)}, trackCount=${mediaItemsToRestore.size}") MediaSession.MediaItemsWithStartPosition( mediaItemsToRestore, @@ -417,6 +427,7 @@ class DashTuneSessionCallback( SessionResult(SessionResult.RESULT_SUCCESS) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to set rating for $mediaId", e) + FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "set_rating") FirebaseUtils.safeRecordException(e) SessionResult(SessionError.ERROR_UNKNOWN) } @@ -425,7 +436,10 @@ class DashTuneSessionCallback( suspend fun sync(): Boolean { if (!::repository.isInitialized) return false - return repository.sync() + FirebaseCrashlytics.getInstance().log("Sync started") + val result = repository.sync() + FirebaseCrashlytics.getInstance().log(if (result) "Sync completed" else "Sync failed") + return result } private suspend fun applyRating(currentMediaItem: String, newRating: Rating) { diff --git a/automotive/src/main/java/com/chamika/dashtune/data/MediaRepository.kt b/automotive/src/main/java/com/chamika/dashtune/data/MediaRepository.kt index 315f7ee..aa42cf2 100644 --- a/automotive/src/main/java/com/chamika/dashtune/data/MediaRepository.kt +++ b/automotive/src/main/java/com/chamika/dashtune/data/MediaRepository.kt @@ -1,9 +1,7 @@ package com.chamika.dashtune.data -import android.content.Context import android.util.Log import androidx.core.net.toUri -import com.chamika.dashtune.AlbumArtContentProvider import androidx.media3.common.HeartRating import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata @@ -11,6 +9,7 @@ import androidx.media3.common.MediaMetadata.MEDIA_TYPE_ALBUM import androidx.media3.common.MediaMetadata.MEDIA_TYPE_ARTIST import androidx.media3.common.MediaMetadata.MEDIA_TYPE_MUSIC import androidx.media3.common.MediaMetadata.MEDIA_TYPE_PLAYLIST +import com.chamika.dashtune.AlbumArtContentProvider import com.chamika.dashtune.Constants.LOG_TAG import com.chamika.dashtune.data.db.CachedMediaItemEntity import com.chamika.dashtune.data.db.MediaCacheDao @@ -18,16 +17,15 @@ import com.chamika.dashtune.media.JellyfinMediaTree import com.chamika.dashtune.media.MediaItemFactory import com.chamika.dashtune.media.MediaItemFactory.Companion.FAVOURITES import com.chamika.dashtune.media.MediaItemFactory.Companion.LATEST_ALBUMS -import com.chamika.dashtune.media.MediaItemFactory.Companion.PARENT_KEY import com.chamika.dashtune.media.MediaItemFactory.Companion.PLAYLISTS import com.chamika.dashtune.media.MediaItemFactory.Companion.RANDOM_ALBUMS import com.chamika.dashtune.media.MediaItemFactory.Companion.ROOT_ID +import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.json.JSONObject class MediaRepository( - private val context: Context, private val dao: MediaCacheDao, private val tree: JellyfinMediaTree, private val itemFactory: MediaItemFactory @@ -82,6 +80,8 @@ class MediaRepository( val allEntities = mutableListOf() var anySuccess = false + FirebaseCrashlytics.getInstance().log("Sync started: ${sectionIds.size} sections") + for (sectionId in sectionIds) { try { val children = tree.getChildren(sectionId) @@ -92,6 +92,8 @@ class MediaRepository( anySuccess = true } catch (e: Exception) { Log.e(LOG_TAG, "Failed to sync section $sectionId", e) + FirebaseCrashlytics.getInstance().setCustomKey("sync_failed_section", sectionId) + FirebaseCrashlytics.getInstance().recordException(e) } } @@ -100,6 +102,7 @@ class MediaRepository( dao.insertAll(allEntities) } + FirebaseCrashlytics.getInstance().log("Sync completed: ${allEntities.size} items, success=$anySuccess") return anySuccess } diff --git a/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt b/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt index 5e2e89e..925f189 100644 --- a/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt +++ b/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope import com.chamika.dashtune.Constants.LOG_TAG import com.chamika.dashtune.FirebaseUtils import com.chamika.dashtune.auth.JellyfinAccountManager +import com.google.firebase.crashlytics.FirebaseCrashlytics import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -49,6 +50,8 @@ class SignInViewModel @Inject constructor() : ViewModel() { response.status == 200 } catch (e: Exception) { Log.w(LOG_TAG, "Error", e) + val host = try { java.net.URI(serverUrl).host ?: "unknown" } catch (_: Exception) { "invalid_url" } + FirebaseCrashlytics.getInstance().setCustomKey("server_url_host", host) FirebaseUtils.safeRecordException(e) false } @@ -103,6 +106,8 @@ class SignInViewModel @Inject constructor() : ViewModel() { } if (loginResponse.status == 200) { + FirebaseCrashlytics.getInstance().setCustomKey("auth_method", "quick_connect") + FirebaseCrashlytics.getInstance().log("Login successful via quick_connect") loginSuccess( server, loginResponse.content.user?.name!!, @@ -119,12 +124,15 @@ class SignInViewModel @Inject constructor() : ViewModel() { } if (response.status == 200) { + FirebaseCrashlytics.getInstance().setCustomKey("auth_method", "password") + FirebaseCrashlytics.getInstance().log("Login successful via password") loginSuccess(server, username, response.content.accessToken!!) } response.status == 200 } catch (e: Exception) { Log.e(LOG_TAG, "Error", e) + FirebaseCrashlytics.getInstance().setCustomKey("auth_method", "password") FirebaseUtils.safeRecordException(e) false } From a7df0ef6467c6c949bcb63020b7f7342977990da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 09:28:01 +0000 Subject: [PATCH 4/5] Refactor: route all Firebase Crashlytics calls through FirebaseUtils Co-authored-by: chamika <754909+chamika@users.noreply.github.com> Agent-Logs-Url: https://github.com/chamika/DashTune/sessions/92f8e656-4f3a-4544-91ef-ec4d3a62aefc --- .../dashtune/AlbumArtContentProvider.kt | 7 ++-- .../chamika/dashtune/DashTuneMusicService.kt | 25 +++++++------ .../dashtune/DashTuneSessionCallback.kt | 25 +++++++------ .../com/chamika/dashtune/FirebaseUtils.kt | 36 +++++++++++++++++++ .../chamika/dashtune/data/MediaRepository.kt | 10 +++--- .../dashtune/signin/SignInViewModel.kt | 13 ++++--- 6 files changed, 74 insertions(+), 42 deletions(-) diff --git a/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt b/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt index ca5662b..d092b23 100644 --- a/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt +++ b/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt @@ -8,8 +8,7 @@ import android.net.Uri import android.os.ParcelFileDescriptor import android.util.Log import com.chamika.dashtune.Constants.LOG_TAG -import com.google.firebase.crashlytics.FirebaseCrashlytics -import okhttp3.OkHttpClient +import com.chamika.dashtune.FirebaseUtils import okhttp3.Request import okio.buffer import okio.sink @@ -101,8 +100,8 @@ class AlbumArtContentProvider : ContentProvider() { tmpFile.renameTo(file) } else { Log.w(LOG_TAG, "Failed to download $remoteUri: \n ${it.code} - ${it.body}") - FirebaseCrashlytics.getInstance().setCustomKey("album_art_path", remoteUri.path ?: "unknown") - FirebaseCrashlytics.getInstance().recordException(Exception("Album art download failed: HTTP ${it.code}")) + FirebaseUtils.safeSetCustomKey("album_art_path", remoteUri.path ?: "unknown") + FirebaseUtils.safeRecordException(Exception("Album art download failed: HTTP ${it.code}")) } inProgress.get(remoteUri)?.countDown() diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt index a5d90f0..53f5f73 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneMusicService.kt @@ -37,7 +37,6 @@ import com.chamika.dashtune.DashTuneSessionCallback.Companion.PLAYLIST_INDEX_PRE import com.chamika.dashtune.DashTuneSessionCallback.Companion.PLAYLIST_TRACK_POSITON_MS_PREF import com.chamika.dashtune.data.db.MediaCacheDao import com.chamika.dashtune.media.MediaItemFactory.Companion.ROOT_ID -import com.google.firebase.crashlytics.FirebaseCrashlytics import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -97,7 +96,7 @@ class DashTuneMusicService : MediaLibraryService() { super.onCreate() Log.i(LOG_TAG, "onCreate") - FirebaseCrashlytics.getInstance().log("Service created") + FirebaseUtils.safeLog("Service created") accountManager = com.chamika.dashtune.auth.JellyfinAccountManager( AccountManager.get(applicationContext) @@ -155,12 +154,12 @@ class DashTuneMusicService : MediaLibraryService() { prefetchNextTracks(player, prefetchCount) } - FirebaseCrashlytics.getInstance().setCustomKey("current_track_id", player.currentMediaItem?.mediaId ?: "") - FirebaseCrashlytics.getInstance().setCustomKey("playlist_size", player.mediaItemCount) + FirebaseUtils.safeSetCustomKey("current_track_id", player.currentMediaItem?.mediaId ?: "") + FirebaseUtils.safeSetCustomKey("playlist_size", player.mediaItemCount) } if (events.contains(Player.EVENT_IS_PLAYING_CHANGED)) { - FirebaseCrashlytics.getInstance().setCustomKey("is_playing", player.isPlaying) + FirebaseUtils.safeSetCustomKey("is_playing", player.isPlaying) if (player.isPlaying) { startPlaybackPoll() } else { @@ -169,14 +168,14 @@ class DashTuneMusicService : MediaLibraryService() { } if (events.contains(Player.EVENT_REPEAT_MODE_CHANGED)) { - FirebaseCrashlytics.getInstance().setCustomKey("repeat_mode", player.repeatMode) + FirebaseUtils.safeSetCustomKey("repeat_mode", player.repeatMode) PreferenceManager.getDefaultSharedPreferences(this@DashTuneMusicService).edit { putInt("repeat_mode", player.repeatMode) } } if (events.contains(Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED)) { - FirebaseCrashlytics.getInstance().setCustomKey("shuffle_enabled", player.shuffleModeEnabled) + FirebaseUtils.safeSetCustomKey("shuffle_enabled", player.shuffleModeEnabled) PreferenceManager.getDefaultSharedPreferences(this@DashTuneMusicService).edit { putBoolean("shuffle_enabled", player.shuffleModeEnabled) } @@ -226,7 +225,7 @@ class DashTuneMusicService : MediaLibraryService() { networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { Log.i(LOG_TAG, "Network available") - FirebaseCrashlytics.getInstance().log("Network available") + FirebaseUtils.safeLog("Network available") if (!accountManager.isAuthenticated) return val prefs = PreferenceManager.getDefaultSharedPreferences(this@DashTuneMusicService) @@ -235,7 +234,7 @@ class DashTuneMusicService : MediaLibraryService() { val syncNeeded = System.currentTimeMillis() - lastSync > sixHoursMs if (syncNeeded) { - FirebaseCrashlytics.getInstance().log("Network available: sync triggered") + FirebaseUtils.safeLog("Network available: sync triggered") serviceScope.launch { val success = callback.sync() if (success) { @@ -249,7 +248,7 @@ class DashTuneMusicService : MediaLibraryService() { } } } else { - FirebaseCrashlytics.getInstance().log("Network available: sync skipped (ran recently)") + FirebaseUtils.safeLog("Network available: sync skipped (ran recently)") } } } @@ -284,7 +283,7 @@ class DashTuneMusicService : MediaLibraryService() { override fun onDestroy() { Log.i(LOG_TAG, "onDestroy") - FirebaseCrashlytics.getInstance().log("Service destroyed") + FirebaseUtils.safeLog("Service destroyed") mediaLibrarySession.release() mediaLibrarySession.player.removeListener(playerListener) @@ -325,7 +324,7 @@ class DashTuneMusicService : MediaLibraryService() { } fun onLogin() { - FirebaseCrashlytics.getInstance().log("Login applied to service") + FirebaseUtils.safeLog("Login applied to service") jellyfinApi.update( baseUrl = accountManager.server, accessToken = accountManager.token @@ -378,7 +377,7 @@ class DashTuneMusicService : MediaLibraryService() { downloadManager.resumeDownloads() } catch (e: Exception) { Log.w(LOG_TAG, "Failed to prefetch: $id", e) - FirebaseCrashlytics.getInstance().setCustomKey("prefetch_track_id", id) + FirebaseUtils.safeSetCustomKey("prefetch_track_id", id) FirebaseUtils.safeRecordException(e) } } diff --git a/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt b/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt index a02ee70..fe8b352 100644 --- a/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt +++ b/automotive/src/main/java/com/chamika/dashtune/DashTuneSessionCallback.kt @@ -36,7 +36,6 @@ import com.chamika.dashtune.signin.SignInActivity import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture -import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import org.jellyfin.sdk.api.client.ApiClient @@ -115,7 +114,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get library root", e) - FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "get_library_root") + FirebaseUtils.safeSetCustomKey("failed_operation", "get_library_root") FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "")) } @@ -149,8 +148,8 @@ class DashTuneSessionCallback( LibraryResult.ofItemList(repository.getChildren(parentId), params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get children for $parentId", e) - FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "get_children") - FirebaseCrashlytics.getInstance().setCustomKey("parent_id", parentId) + FirebaseUtils.safeSetCustomKey("failed_operation", "get_children") + FirebaseUtils.safeSetCustomKey("parent_id", parentId) FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "Failed to get media items")) } @@ -193,7 +192,7 @@ class DashTuneSessionCallback( ) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get item $mediaId", e) - FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "get_item") + FirebaseUtils.safeSetCustomKey("failed_operation", "get_item") FirebaseUtils.safeRecordException(e) LibraryResult.ofError(SessionError(SessionError.ERROR_UNKNOWN, "Failed to get media item")) } @@ -293,8 +292,8 @@ class DashTuneSessionCallback( LibraryResult.ofVoid(params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to search for '$query'", e) - FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "search") - FirebaseCrashlytics.getInstance().setCustomKey("search_query", query) + FirebaseUtils.safeSetCustomKey("failed_operation", "search") + FirebaseUtils.safeSetCustomKey("search_query", query) FirebaseUtils.safeRecordException(e) LibraryResult.ofVoid() } @@ -315,8 +314,8 @@ class DashTuneSessionCallback( LibraryResult.ofItemList(results, params) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to get search results for '$query'", e) - FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "get_search_result") - FirebaseCrashlytics.getInstance().setCustomKey("search_query", query) + FirebaseUtils.safeSetCustomKey("failed_operation", "get_search_result") + FirebaseUtils.safeSetCustomKey("search_query", query) FirebaseUtils.safeRecordException(e) LibraryResult.ofItemList(emptyList(), params) } @@ -343,7 +342,7 @@ class DashTuneSessionCallback( ?.awaitAll() ?: listOf() Log.d(LOG_TAG, "Resuming playback with $mediaItemsToRestore") - FirebaseCrashlytics.getInstance().log("Restoring playback: index=${prefs.getInt(PLAYLIST_INDEX_PREF, 0)}, trackCount=${mediaItemsToRestore.size}") + FirebaseUtils.safeLog("Restoring playback: index=${prefs.getInt(PLAYLIST_INDEX_PREF, 0)}, trackCount=${mediaItemsToRestore.size}") MediaSession.MediaItemsWithStartPosition( mediaItemsToRestore, @@ -427,7 +426,7 @@ class DashTuneSessionCallback( SessionResult(SessionResult.RESULT_SUCCESS) } catch (e: Exception) { Log.e(LOG_TAG, "Failed to set rating for $mediaId", e) - FirebaseCrashlytics.getInstance().setCustomKey("failed_operation", "set_rating") + FirebaseUtils.safeSetCustomKey("failed_operation", "set_rating") FirebaseUtils.safeRecordException(e) SessionResult(SessionError.ERROR_UNKNOWN) } @@ -436,9 +435,9 @@ class DashTuneSessionCallback( suspend fun sync(): Boolean { if (!::repository.isInitialized) return false - FirebaseCrashlytics.getInstance().log("Sync started") + FirebaseUtils.safeLog("Sync started") val result = repository.sync() - FirebaseCrashlytics.getInstance().log(if (result) "Sync completed" else "Sync failed") + FirebaseUtils.safeLog(if (result) "Sync completed" else "Sync failed") return result } diff --git a/automotive/src/main/java/com/chamika/dashtune/FirebaseUtils.kt b/automotive/src/main/java/com/chamika/dashtune/FirebaseUtils.kt index 8d9d800..8aaa005 100644 --- a/automotive/src/main/java/com/chamika/dashtune/FirebaseUtils.kt +++ b/automotive/src/main/java/com/chamika/dashtune/FirebaseUtils.kt @@ -17,4 +17,40 @@ object FirebaseUtils { Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", crashlyticsError) } } + + /** Logs [message] to Firebase Crashlytics, safe against GMS unavailability. */ + fun safeLog(message: String) { + try { + FirebaseCrashlytics.getInstance().log(message) + } catch (e: Exception) { + Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", e) + } + } + + /** Sets a string custom key on Firebase Crashlytics, safe against GMS unavailability. */ + fun safeSetCustomKey(key: String, value: String) { + try { + FirebaseCrashlytics.getInstance().setCustomKey(key, value) + } catch (e: Exception) { + Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", e) + } + } + + /** Sets an int custom key on Firebase Crashlytics, safe against GMS unavailability. */ + fun safeSetCustomKey(key: String, value: Int) { + try { + FirebaseCrashlytics.getInstance().setCustomKey(key, value) + } catch (e: Exception) { + Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", e) + } + } + + /** Sets a boolean custom key on Firebase Crashlytics, safe against GMS unavailability. */ + fun safeSetCustomKey(key: String, value: Boolean) { + try { + FirebaseCrashlytics.getInstance().setCustomKey(key, value) + } catch (e: Exception) { + Log.w(LOG_TAG, "Crashlytics unavailable – Google Play Services not ready yet", e) + } + } } diff --git a/automotive/src/main/java/com/chamika/dashtune/data/MediaRepository.kt b/automotive/src/main/java/com/chamika/dashtune/data/MediaRepository.kt index aa42cf2..c669391 100644 --- a/automotive/src/main/java/com/chamika/dashtune/data/MediaRepository.kt +++ b/automotive/src/main/java/com/chamika/dashtune/data/MediaRepository.kt @@ -20,7 +20,7 @@ import com.chamika.dashtune.media.MediaItemFactory.Companion.LATEST_ALBUMS import com.chamika.dashtune.media.MediaItemFactory.Companion.PLAYLISTS import com.chamika.dashtune.media.MediaItemFactory.Companion.RANDOM_ALBUMS import com.chamika.dashtune.media.MediaItemFactory.Companion.ROOT_ID -import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.chamika.dashtune.FirebaseUtils import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.json.JSONObject @@ -80,7 +80,7 @@ class MediaRepository( val allEntities = mutableListOf() var anySuccess = false - FirebaseCrashlytics.getInstance().log("Sync started: ${sectionIds.size} sections") + FirebaseUtils.safeLog("Sync started: ${sectionIds.size} sections") for (sectionId in sectionIds) { try { @@ -92,8 +92,8 @@ class MediaRepository( anySuccess = true } catch (e: Exception) { Log.e(LOG_TAG, "Failed to sync section $sectionId", e) - FirebaseCrashlytics.getInstance().setCustomKey("sync_failed_section", sectionId) - FirebaseCrashlytics.getInstance().recordException(e) + FirebaseUtils.safeSetCustomKey("sync_failed_section", sectionId) + FirebaseUtils.safeRecordException(e) } } @@ -102,7 +102,7 @@ class MediaRepository( dao.insertAll(allEntities) } - FirebaseCrashlytics.getInstance().log("Sync completed: ${allEntities.size} items, success=$anySuccess") + FirebaseUtils.safeLog("Sync completed: ${allEntities.size} items, success=$anySuccess") return anySuccess } diff --git a/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt b/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt index 925f189..e0cc6cf 100644 --- a/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt +++ b/automotive/src/main/java/com/chamika/dashtune/signin/SignInViewModel.kt @@ -8,7 +8,6 @@ import androidx.lifecycle.viewModelScope import com.chamika.dashtune.Constants.LOG_TAG import com.chamika.dashtune.FirebaseUtils import com.chamika.dashtune.auth.JellyfinAccountManager -import com.google.firebase.crashlytics.FirebaseCrashlytics import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -51,7 +50,7 @@ class SignInViewModel @Inject constructor() : ViewModel() { } catch (e: Exception) { Log.w(LOG_TAG, "Error", e) val host = try { java.net.URI(serverUrl).host ?: "unknown" } catch (_: Exception) { "invalid_url" } - FirebaseCrashlytics.getInstance().setCustomKey("server_url_host", host) + FirebaseUtils.safeSetCustomKey("server_url_host", host) FirebaseUtils.safeRecordException(e) false } @@ -106,8 +105,8 @@ class SignInViewModel @Inject constructor() : ViewModel() { } if (loginResponse.status == 200) { - FirebaseCrashlytics.getInstance().setCustomKey("auth_method", "quick_connect") - FirebaseCrashlytics.getInstance().log("Login successful via quick_connect") + FirebaseUtils.safeSetCustomKey("auth_method", "quick_connect") + FirebaseUtils.safeLog("Login successful via quick_connect") loginSuccess( server, loginResponse.content.user?.name!!, @@ -124,15 +123,15 @@ class SignInViewModel @Inject constructor() : ViewModel() { } if (response.status == 200) { - FirebaseCrashlytics.getInstance().setCustomKey("auth_method", "password") - FirebaseCrashlytics.getInstance().log("Login successful via password") + FirebaseUtils.safeSetCustomKey("auth_method", "password") + FirebaseUtils.safeLog("Login successful via password") loginSuccess(server, username, response.content.accessToken!!) } response.status == 200 } catch (e: Exception) { Log.e(LOG_TAG, "Error", e) - FirebaseCrashlytics.getInstance().setCustomKey("auth_method", "password") + FirebaseUtils.safeSetCustomKey("auth_method", "password") FirebaseUtils.safeRecordException(e) false } From aaa2f8a2d33482617720f4765c374681088c416f Mon Sep 17 00:00:00 2001 From: Chamika Date: Sun, 22 Mar 2026 09:50:52 +0000 Subject: [PATCH 5/5] Fix compilation issues --- .../main/java/com/chamika/dashtune/AlbumArtContentProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt b/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt index d092b23..74be0a8 100644 --- a/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt +++ b/automotive/src/main/java/com/chamika/dashtune/AlbumArtContentProvider.kt @@ -8,7 +8,7 @@ import android.net.Uri import android.os.ParcelFileDescriptor import android.util.Log import com.chamika.dashtune.Constants.LOG_TAG -import com.chamika.dashtune.FirebaseUtils +import okhttp3.OkHttpClient import okhttp3.Request import okio.buffer import okio.sink