Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ dependencies {

implementation(platform(libs.firebase.bom))
implementation(libs.firebase.crashlytics)
implementation(libs.timber)
implementation(libs.firebase.ai)
implementation(libs.firebase.app.check)
implementation(libs.firebase.config)
Expand Down
8 changes: 8 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@

# Ignore missing Java SE annotation processing classes, often from libraries like AutoValue
-dontwarn javax.lang.model.**

# OkHttp
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**

# Ignore SAX parser warning
-dontwarn org.xml.sax.**
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject

@HiltAndroidApp
class AndroidifyApplication : Application(), SingletonImageLoader.Factory {

Expand All @@ -32,6 +34,11 @@ class AndroidifyApplication : Application(), SingletonImageLoader.Factory {

override fun onCreate() {
super.onCreate()
if (isDebuggable()) {
Timber.plant(Timber.DebugTree())
} else {
Timber.plant(CrashlyticsTree())
}
setStrictModePolicy()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.developers.androidify

import android.util.Log
import com.google.firebase.crashlytics.FirebaseCrashlytics
import timber.log.Timber

/**
* A Timber tree that logs to Crashlytics.
*
* In debug builds, this tree does nothing. In release builds, it logs non-fatal exceptions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious as I don't know this - do fatal exceptions automatically end up in Play Console anyway?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, those are already logged :)

* to Crashlytics.
*/
class CrashlyticsTree : Timber.Tree() {

private val crashlytics by lazy { FirebaseCrashlytics.getInstance() }

override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return
}

crashlytics.log(message)

if (t != null) {
crashlytics.recordException(t)
}
}
}
1 change: 1 addition & 0 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ dependencies {
implementation(libs.coil.compose.http)
implementation(libs.coil.gif)
implementation(platform(libs.firebase.bom))
implementation(libs.timber)
implementation(libs.firebase.ai)
implementation(libs.firebase.analytics) {
exclude(group = "com.google.guava")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package com.android.developers.androidify.ondevice

import android.graphics.Bitmap
import android.util.Log
import com.google.android.gms.common.moduleinstall.InstallStatusListener
import com.google.android.gms.common.moduleinstall.ModuleInstallClient
import com.google.android.gms.common.moduleinstall.ModuleInstallRequest
Expand All @@ -28,6 +27,7 @@ import com.google.mlkit.vision.segmentation.subject.SubjectSegmentation
import com.google.mlkit.vision.segmentation.subject.SubjectSegmenterOptions
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
import timber.log.Timber
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
Expand Down Expand Up @@ -64,7 +64,7 @@ class LocalSegmentationDataSourceImpl @Inject constructor(
) : InstallStatusListener {

override fun onInstallStatusUpdated(update: ModuleInstallStatusUpdate) {
Log.d("LocalSegmentationDataSource", "Download progress: ${update.installState}.. ${continuation.hashCode()} ${continuation.isActive}")
Timber.d("Download progress: ${update.installState}.. ${continuation.hashCode()} ${continuation.isActive}")
if (!continuation.isActive) return
if (update.installState == ModuleInstallStatusUpdate.InstallState.STATE_COMPLETED) {
continuation.resume(true)
Expand All @@ -73,7 +73,7 @@ class LocalSegmentationDataSourceImpl @Inject constructor(
ImageSegmentationException("Module download failed or was canceled. State: ${update.installState}"),
)
} else {
Log.d("LocalSegmentationDataSource", "State update: ${update.installState}")
Timber.d("State update: ${update.installState}")
}
}
}
Expand All @@ -88,11 +88,11 @@ class LocalSegmentationDataSourceImpl @Inject constructor(
moduleInstallClient
.installModules(moduleInstallRequest)
.addOnFailureListener {
Log.e("LocalSegmentationDataSource", "Failed to download module", it)
Timber.e(it, "Failed to download module")
continuation.resumeWithException(it)
}
.addOnCompleteListener {
Log.d("LocalSegmentationDataSource", "Successfully triggered download - await download progress updates")
Timber.d("Successfully triggered download - await download progress updates")
}
}
return result
Expand All @@ -102,13 +102,13 @@ class LocalSegmentationDataSourceImpl @Inject constructor(
val areModulesAvailable = isSubjectSegmentationModuleInstalled()

if (!areModulesAvailable) {
Log.d("LocalSegmentationDataSource", "Modules not available - downloading")
Timber.d("Modules not available - downloading")
val result = installSubjectSegmentationModule()
if (!result) {
throw Exception("Failed to download module")
}
} else {
Log.d("LocalSegmentationDataSource", "Modules available")
Timber.d("Modules available")
}
val image = InputImage.fromBitmap(bitmap, 0)
return suspendCancellableCoroutine { continuation ->
Expand All @@ -121,7 +121,7 @@ class LocalSegmentationDataSourceImpl @Inject constructor(
}
}
.addOnFailureListener { e ->
Log.e("LocalSegmentationDataSource", "Exception while executing background removal", e)
Timber.e(e, "Exception while executing background removal")
continuation.resumeWithException(e)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,48 @@ package com.android.developers.androidify.startup

import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.startup.Initializer
import com.android.developers.androidify.network.BuildConfig
import com.google.firebase.Firebase
import com.google.firebase.appcheck.FirebaseAppCheck
import com.google.firebase.appcheck.appCheck
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory
import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory
import timber.log.Timber

/**
* Initialize [FirebaseAppCheck] using the App Startup Library.
*/
@SuppressLint("EnsureInitializerMetadata") // Registered in :app module
class FirebaseAppCheckInitializer : Initializer<FirebaseAppCheck> {
override fun create(context: Context): FirebaseAppCheck {
return Firebase.appCheck.apply {
val appCheck = Firebase.appCheck.apply {
if (BuildConfig.DEBUG) {
Log.i("AndroidifyAppCheck", "Firebase debug")
Timber.i(
"Installing Firebase debug, ensure your " +
"debug token is registered on Firebase Console",
)
installAppCheckProviderFactory(
DebugAppCheckProviderFactory.getInstance(),
)
} else {
Log.i("AndroidifyAppCheck", "Play integrity")
Timber.i("Play integrity installing...")
installAppCheckProviderFactory(
PlayIntegrityAppCheckProviderFactory.getInstance(),
)
}
setTokenAutoRefreshEnabled(true)
}

val token = appCheck.getAppCheckToken(false)
token.addOnCompleteListener {
if (token.isSuccessful) {
Timber.i("Firebase app check token success: ${token.result.expireTimeMillis}")
} else {
Timber.e(token.exception, "Firebase app check token failure")
}
}
return appCheck
}

override fun dependencies(): List<Class<out Initializer<*>?>?> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ package com.android.developers.androidify.startup

import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.startup.Initializer
import com.android.developers.androidify.network.R
import com.google.firebase.Firebase
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.remoteConfig
import com.google.firebase.remoteconfig.remoteConfigSettings
import timber.log.Timber

/**
* Initialize [FirebaseRemoteConfig] using the App Startup Library.
Expand All @@ -39,10 +39,10 @@ class FirebaseRemoteConfigInitializer : Initializer<FirebaseRemoteConfig> {
setDefaultsAsync(R.xml.remote_config_defaults)
fetchAndActivate()
.addOnSuccessListener {
Log.d("FirebaseRemoteConfig", "Config params updated: $it")
Timber.d("Config params updated: $it")
}
.addOnFailureListener {
Log.d("FirebaseRemoteConfig", "Config params failed: $it")
Timber.d("Config params failed: $it")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.developers.testing.repository

import android.graphics.Bitmap
Expand Down Expand Up @@ -65,4 +80,4 @@ class FakeWatchFaceInstallationRepository : WatchFaceInstallationRepository {
public fun setWatchAsConnected() {
_connectedWatch.value = watch
}
}
}
4 changes: 2 additions & 2 deletions core/theme/src/main/res/drawable/squiggle_full.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2025 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -13,7 +14,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The squiggle but cropped to the viewport. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1425dp"
android:height="822dp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ fun MainPanelWorkaround() {
session?.scene?.mainPanelEntity?.setEnabled(false)
}
}
}
}
1 change: 1 addition & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {

implementation(libs.kotlinx.serialization.json)
implementation(libs.retrofit)
implementation(libs.timber)
implementation(libs.converter.gson)
implementation(libs.logging.interceptor)
implementation(libs.hilt.android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
package com.android.developers.androidify.data

import android.app.Application
import android.util.Log
import com.google.ai.edge.aicore.DownloadCallback
import com.google.ai.edge.aicore.DownloadConfig
import com.google.ai.edge.aicore.GenerativeAIException
import com.google.ai.edge.aicore.GenerativeModel
import com.google.ai.edge.aicore.generationConfig
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -35,34 +35,34 @@ class GeminiNanoDownloader @Inject constructor(private val application: Applicat
fun isModelDownloaded() = modelDownloaded

suspend fun downloadModel() {
Log.d("GeminiNanoDownloader", "downloadModel")
Timber.d("downloadModel")
try {
setup()
generativeModel?.prepareInferenceEngine()
} catch (e: Exception) {
Log.e("GeminiNanoDownloader", "Error preparing inference engine", e)
Timber.e(e, "Error preparing inference engine")
}
Log.d("GeminiNanoDownloader", "prepare inference engine")
Timber.d("prepare inference engine")
}

private fun setup() {
val downloadCallback = object : DownloadCallback {
override fun onDownloadStarted(bytesToDownload: Long) {
super.onDownloadStarted(bytesToDownload)
Log.i("GeminiNanoDownloader", "onDownloadStarted for Gemini Nano $bytesToDownload")
Timber.i("onDownloadStarted for Gemini Nano $bytesToDownload")
}

override fun onDownloadCompleted() {
super.onDownloadCompleted()
modelDownloaded = true
Log.i("GeminiNanoDownloader", "onDownloadCompleted for Gemini Nano")
Timber.i("onDownloadCompleted for Gemini Nano")
}

override fun onDownloadFailed(failureStatus: String, e: GenerativeAIException) {
super.onDownloadFailed(failureStatus, e)
// downloading the model has failed so make the model null as we can't use it
generativeModel = null
Log.i("GeminiNanoDownloader", "onDownloadFailed for Gemini Nano")
Timber.i("onDownloadFailed for Gemini Nano")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
package com.android.developers.androidify.data

import android.util.Log
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -39,7 +39,7 @@ internal class GeminiNanoGenerationDataSourceImpl @Inject constructor(private va
override suspend fun generatePrompt(prompt: String): String? {
if (!downloader.isModelDownloaded()) return null
val response = downloader.generativeModel?.generateContent(prompt)
Log.d("GeminiNanoGenerationDataSource", "generatePrompt: ${response?.text}")
Timber.d("generatePrompt: ${response?.text}")
return response?.text
}
}
Loading