diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index adddb28..3ded03c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -88,17 +88,20 @@ jobs: #- name: "Write Google Services File" # run: | # echo "${{ secrets.GOOGLE_SERVICES }}" | base64 --decode > app/google-services.json - # # stat app/google-services.json + - name: "Write Secret Properties" + run: | + echo "${{ secrets.SECRET_PROPERTIES }}" | base64 --decode > secret.properties + wc -l secret.properties + - name: "Write Keystore File" run: | echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 --decode > ${{ env.key_file }} - echo ${{ secrets.ANDROID_KEYSTORE_PASS }} | keytool -list -keystore ${{ env.key_file }} #- name: "Setup Node 24" - # uses: actions/setup-node@v6 + # uses: actions/setup-node@v7 # with: # node-version: 24 # @@ -194,7 +197,7 @@ jobs: # #- name: "Upload Bundle to Artifacts" # if: ${{ !github.event.release.prerelease }} - # uses: actions/upload-artifact@v6 + # uses: actions/upload-artifact@v7 # with: # name: bundle # path: ${{ env.aab_path }} diff --git a/.gitignore b/.gitignore index 56fa412..9b1903f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ app/debug app/release # App local.properties +secret.properties *.keystore *.logcat **/google-services.json diff --git a/AGENTS.md b/AGENTS.md index 244d8aa..43f5ee0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,20 +30,12 @@ ALWAYS use the `task *` commands | `task check` | Prettier check (check non-kotlin files) | | `task format` | Prettier write (format non-kotlin files) | -Do NOT use `-q` or pipe Gradle output through `Select-Object` — both hide progress and make long builds look hung. +Do NOT run task compile/debug/release/bundle every turn unless it is REQUIRED!!! ## Testing To test on a device use the `adb` command. If no devices are running and attached, ask the user to do this! -## Rules - -Do NOT run task compile/debug/release/bundle after making edits unless it is REQUIRED!!! - -# Application - -## Testing - DO NOT uninstall the application to clear data, use: `adb shell pm clear` For testing 304's you can use any image returned by: https://images.cssnr.com/aviation diff --git a/README.md b/README.md index b25c5cb..db5f3ec 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,41 @@ If you run into any issues or need help getting started, please do one of the fo [![Discussions](https://img.shields.io/badge/discussions-blue?style=for-the-badge&logo=livechat&logoColor=white)](https://github.com/cssnr/remote-wallpaper-android/discussions) [![Discord](https://img.shields.io/badge/discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/wXy6m2X8wY) +## Crash Reporting + +> [!NOTE] +> This feature is currently in [beta](https://github.com/cssnr/remote-wallpaper-android/releases) testing. +> To provide feedback join the [Discussion](https://github.com/cssnr/remote-wallpaper-android/discussions/42). + +Without crash reporting, fixing a bug requires you to: + +- Stop what you're doing and open a browser +- Go to the GitHub repo and create an Issue +- Explain exactly what you were doing when the app crashed +- Hope I can re-create the bug myself to get the stack trace + +That's a heavy ask for an app that's already broken — it leaves you with a bad experience and +me without enough data to fix it. + +To close that gap without compromising your data or privacy, this app uses +[ACRA](https://github.com/ACRA/acra) — an open-source crash reporting library. Reports are received by a +self-hosted [Acrarium](https://github.com/F43nd1r/Acrarium) backend that runs on my own infrastructure, +so crash data doesn't go to any third parties — no Google or other big-data services. + +**You can turn crash reporting on or off at any time with a toggle on the Settings page.** + +### What Gets Collected + +ACRA only sends reports when the app hits an unhandled crash. By default, +it only sends the technical context needed to diagnose the crash: + +- The **stack trace** of the crash, plus the app and Android versions +- Basic **device context** — e.g. the device model and OS version +- A short extract of the app's **own logcat** (the last ~200 lines) + +It does **not** track usage or activity, collect a device identifier, or send system or other apps' +logs. Each report is **anonymized** and sent directly to my server, so only I receive the data. + # Development Documentation for building the [app](#app) and [docs](#docs). diff --git a/TODO.md b/TODO.md index d70f2b7..8a7857e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,11 @@ # TODO -## Fixes Required +## ACRA Release + +- Add `Crash Reporting` to the [docs](docs) +- Update `acra_info_link` in [strings.xml](app/src/main/res/values/strings.xml) + +## Required Fixes ### Intent.ACTION_VIEW diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0f15e16..50bfcbb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,12 @@ import com.android.build.api.dsl.ApplicationExtension import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.util.Properties + +// ACRA - Load credentials from secret.properties +val secretProperties = Properties().apply { + val file = rootProject.file("secret.properties") + if (file.exists()) file.inputStream().use { load(it) } +} plugins { alias(libs.plugins.android.application) @@ -21,6 +28,12 @@ configure { versionName = "0.0.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + // ACRA - Acrarium backend setup: https://www.acra.ch/docs/Setup + buildConfigField("String", "ACRA_URI", "\"${secretProperties.getProperty("acra.uri") ?: ""}\"") + buildConfigField("String", "ACRA_USER", "\"${secretProperties.getProperty("acra.user") ?: ""}\"") + buildConfigField("String", "ACRA_PASS", "\"${secretProperties.getProperty("acra.pass") ?: ""}\"") + manifestPlaceholders["firebaseAnalyticsDeactivated"] = false // enabled manifestPlaceholders["firebaseCrashlyticsEnabled"] = true // enabled } @@ -48,6 +61,7 @@ configure { buildFeatures { viewBinding = true + buildConfig = true } } @@ -77,6 +91,8 @@ dependencies { implementation(libs.retrofit) implementation(libs.converter.moshi) implementation(libs.photoview) + implementation(libs.acra.http) + implementation(libs.acra.toast) ksp(libs.moshi.kotlin.codegen) ksp(libs.androidx.room.compiler) testImplementation(libs.junit) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1955a16..63b9016 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ ("acra.enable") + acraEnable?.setOnPreferenceChangeListener { _, newValue -> + Log.d("SettingsFragment", "acra.enable: $newValue") + ctx.toggleAcra(acraEnable, newValue) + false + } } private fun updateWorkIntervalSettings(selectedValue: String?) { @@ -142,6 +150,32 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + private fun Context.toggleAcra(switchPreference: SwitchPreferenceCompat, newValue: Any) { + Log.d("SettingsFragment", "toggleAcra: $newValue") + if (newValue as Boolean) { + Log.d("SettingsFragment", "ENABLE ACRA") + switchPreference.isChecked = true + } else { + MaterialAlertDialogBuilder(this) + .setTitle("Please Reconsider") + .setMessage(getString(R.string.acra_disable_message)) + //.setMessage(Html.fromHtml(getString(R.string.acra_disable_message), Html.FROM_HTML_MODE_LEGACY)) + .setNeutralButton("More Info") { _, _ -> + startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.acra_info_link).toUri())) + } + .setPositiveButton("Disable") { _, _ -> + Log.d("SettingsFragment", "DISABLE ACRA") + switchPreference.isChecked = false + } + .setNegativeButton("Cancel", null) + .show() + //.apply { + // findViewById(android.R.id.message)?.movementMethod = + // LinkMovementMethod.getInstance() + //} + } + } + private fun Context.updateWorkManager(newValue: String?, curValue: String? = null): Boolean { Log.i("updateWorkManager", "newValue: $newValue - curValue: $curValue") if (newValue.isNullOrEmpty()) { diff --git a/app/src/main/res/drawable/md_frame_bug_24px.xml b/app/src/main/res/drawable/md_frame_bug_24px.xml new file mode 100644 index 0000000..2c9c53e --- /dev/null +++ b/app/src/main/res/drawable/md_frame_bug_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 38e8da6..cd0e774 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -65,4 +65,11 @@ black liberty + + + App crashed and report sent. Disable reporting in Settings. + + + Reports are only sent when the app crashes. This helps fix bugs and no personal data is sent. + https://github.com/cssnr/remote-wallpaper-android#crash-reporting diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 28c1c82..e413e85 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -76,4 +76,16 @@ + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 88ab14f..7adeb94 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ okhttp = "5.5.0" retrofit = "3.0.0" moshiKotlinCodegen = "1.15.2" photoview = "1.0.3" +acra = "5.13.1" # taptargetview = "1.15.0" # googleServices = "4.4.2" @@ -59,6 +60,8 @@ retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = converter-moshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "retrofit" } moshi-kotlin-codegen = { group = "com.squareup.moshi", name = "moshi-kotlin-codegen", version.ref = "moshiKotlinCodegen" } photoview = { group = "io.getstream", name = "photoview", version.ref = "photoview" } +acra-http = { group = "ch.acra", name = "acra-http", version.ref = "acra" } +acra-toast = { group = "ch.acra", name = "acra-toast", version.ref = "acra" } # taptargetview = { group = "com.getkeepsafe.taptargetview", name = "taptargetview", version.ref = "taptargetview" } # firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }