From ac208572866ab82c45ba5139a958f220cf840529 Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Wed, 23 Sep 2026 14:39:15 +0200 Subject: [PATCH 1/9] feat(build): First step towards R8 minify - still crashing With keeping the apache httpclient, webdav and ical4j, the app at least starts. But it crashes when clicking the plus button (OCFileListBottomSheetDialog). Related to #17640 Assisted-by: GithubCopilot:GPT-5.4 Signed-off-by: Philipp Hasper --- app/build.gradle.kts | 7 ++++++- app/proguard-rules.pro | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 app/proguard-rules.pro diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c4e8f4e1aba2..80b38fa0c88e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -142,9 +142,14 @@ android { buildTypes { release { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) buildConfigField("String", "NC_TEST_SERVER_DATA_STRING", "\"\"") } - debug { enableUnitTestCoverage = project.hasProperty("coverage") enableAndroidTestCoverage = project.hasProperty("coverage") diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 000000000000..3817ac49aca7 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,24 @@ +# Nextcloud - Android Client +# +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. + +# Silence missing-class warnings from optional transitive dependencies and Android/JDK API gaps. +-dontwarn com.gemalto.jp2.JP2Decoder +-dontwarn com.gemalto.jp2.JP2Encoder +-dontwarn java.time.zone.ZoneRulesProvider +-dontwarn org.joda.convert.ToString + +# Commons HttpClient is still used at runtime, so keep these packages from being stripped or renamed. +-keep class org.apache.commons.httpclient.* { *; } +-keep class org.apache.commons.httpclient.auth.** { *; } + +# Some libraries load .properties resources relative to their package. +# Keeping the package names preserves Class.getResourceAsStream(...) lookups after shrinking. +-keeppackagenames org.apache.jackrabbit.webdav +-keeppackagenames net.fortuna.ical4j +-keep class org.apache.commons.httpclient.cookie.** { *; } From d5683adeb469ce584ae11271c832cfbd63713a68 Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Wed, 23 Sep 2026 16:09:50 +0200 Subject: [PATCH 2/9] feat(build): gradle property to control optimization, set for CI This adds a property to enable the optimization, and it is activated for the CI - build - screenshot tests - instrumentation tests - unit test (though probably without effect) For this to work, it must also be present in the debug build variant Signed-off-by: Philipp Hasper --- .github/workflows/assembleFlavors.yml | 3 +-- .github/workflows/unit-tests.yml | 2 +- app/build.gradle.kts | 12 +++++++++--- scripts/runAllScreenshotCombinations | 2 ++ scripts/runCombinedTest.sh | 3 ++- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/assembleFlavors.yml b/.github/workflows/assembleFlavors.yml index a6ed7cc24dff..89ba5ac3ad41 100644 --- a/.github/workflows/assembleFlavors.yml +++ b/.github/workflows/assembleFlavors.yml @@ -47,5 +47,4 @@ jobs: echo "kotlin.daemon.jvmargs=-Xmx3g -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m" echo "org.gradle.workers.max=2" } >> gradle.properties - ./gradlew ${{ matrix.tasks }} - + ./gradlew ${{ matrix.tasks }} -Pminify diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 11f934a1da3b..7330ed6d05cb 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -40,7 +40,7 @@ jobs: uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 - name: Run unit tests with coverage - run: ./gradlew jacocoTestGplayDebugUnitTest + run: ./gradlew jacocoTestGplayDebugUnitTest -Pminify - name: Upload failing results if: ${{ failure() }} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 80b38fa0c88e..0f701f218871 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,6 +20,8 @@ val shotTest = System.getenv("SHOT_TEST") == "true" val ciBuild = System.getenv("CI") == "true" val perfAnalysis = project.hasProperty("perfAnalysis") +val minify = project.hasProperty("minify") // shall be off by default + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) @@ -141,16 +143,20 @@ android { flavorDimensions += "default" buildTypes { - release { - isMinifyEnabled = true - isShrinkResources = true + fun com.android.build.api.dsl.BuildType.commonMinifyConfig() { + isMinifyEnabled = minify + isShrinkResources = minify proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + } + release { + commonMinifyConfig() buildConfigField("String", "NC_TEST_SERVER_DATA_STRING", "\"\"") } debug { + commonMinifyConfig() enableUnitTestCoverage = project.hasProperty("coverage") enableAndroidTestCoverage = project.hasProperty("coverage") resConfigs("xxxhdpi") diff --git a/scripts/runAllScreenshotCombinations b/scripts/runAllScreenshotCombinations index 6e208d20eed0..a0d7caf99950 100755 --- a/scripts/runAllScreenshotCombinations +++ b/scripts/runAllScreenshotCombinations @@ -30,6 +30,7 @@ do if [[ $1 = "noCI" ]]; then ./gradlew --console plain genericDebugExecuteScreenshotTests \ $record \ + -Pminify \ -Pscreenshot=true \ -Pandroid.testInstrumentationRunnerArguments.annotation=com.owncloud.android.utils.ScreenshotTest \ -Pandroid.testInstrumentationRunnerArguments.COLOR="$color" \ @@ -43,6 +44,7 @@ do else ./gradlew --console plain genericDebugExecuteScreenshotTests \ $record \ + -Pminify \ -Pandroid.testInstrumentationRunnerArguments.annotation=com.owncloud.android.utils.ScreenshotTest \ -Pandroid.testInstrumentationRunnerArguments.COLOR="$color" \ -Pandroid.testInstrumentationRunnerArguments.DARKMODE="$darkMode" /dev/null \ diff --git a/scripts/runCombinedTest.sh b/scripts/runCombinedTest.sh index 86ecf0cc6763..29c12989b0a6 100755 --- a/scripts/runCombinedTest.sh +++ b/scripts/runCombinedTest.sh @@ -24,12 +24,13 @@ scripts/deleteOldComments.sh "$BRANCH" "IT" "$DRONE_PULL_REQUEST" scripts/wait_for_emulator.sh || exit 1 -./gradlew installGplayDebugAndroidTest +./gradlew installGplayDebugAndroidTest -Pminify gradle_arguments=( -Pcoverage -Pandroid.testInstrumentationRunnerArguments.notAnnotation=com.owncloud.android.utils.ScreenshotTest -Pandroid.testInstrumentationRunnerArguments.filter=com.nextcloud.test.FlakyTestFilter,com.nextcloud.test.ServerVersionFilter + -Pminify ) if [[ "$BRANCH" =~ ^stable([0-9]+)$ ]]; then From 6fc208841cfcbb75528d460414c9ea1c7bb4c766 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 6 Oct 2026 14:26:01 +0200 Subject: [PATCH 3/9] add new rules Signed-off-by: alperozturk96 --- AGENTS.md | 6 ++++ app/build.gradle.kts | 18 ++++------- app/proguard-rules.pro | 24 -------------- .../preferences/AppPreferencesImpl.java | 2 +- app/src/main/keepRules/ezvcard.keep | 14 ++++++++ app/src/main/keepRules/gson-models.keep | 32 +++++++++++++++++++ app/src/main/keepRules/ical4j.keep | 22 +++++++++++++ app/src/main/keepRules/legacy-http.keep | 19 +++++++++++ app/src/main/keepRules/nextcloud-library.keep | 21 ++++++++++++ app/src/main/keepRules/sso.keep | 17 ++++++++++ 10 files changed, 139 insertions(+), 36 deletions(-) delete mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/keepRules/ezvcard.keep create mode 100644 app/src/main/keepRules/gson-models.keep create mode 100644 app/src/main/keepRules/ical4j.keep create mode 100644 app/src/main/keepRules/legacy-http.keep create mode 100644 app/src/main/keepRules/nextcloud-library.keep create mode 100644 app/src/main/keepRules/sso.keep diff --git a/AGENTS.md b/AGENTS.md index 79f9715c3bdf..0001287a47a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,6 +39,12 @@ Project-specific skills live in `./.claude/skills//`. Load a skill w - **`android-java-to-kotlin`** (`./.claude/skills/android-java-to-kotlin/SKILL.md`) — Completes a Java-to-Kotlin conversion in this Android app. Use it when finishing a conversion, when the user mentions "java to kotlin", "j2k", "convert java", or "make it idiomatic", or when a freshly IDE-converted `.kt` file needs cleanup. The workflow is two-person: the developer first runs the Android Studio converter (`Code > Convert Java File to Kotlin File`), then the agent drives the idiomatic second pass — fail-fast control flow, function decomposition, `lifecycleScope`/coroutines instead of Java threads, modern Android APIs, and project conventions (SPDX headers, no magic numbers, `@JvmStatic`). The conversion must preserve behaviour, and the agent must write a behaviour-locking test before declaring it done. - **`android-activity-to-navigator-fragment`** (`./.claude/skills/android-activity-to-navigator-fragment/SKILL.md`) — Converts a standalone Activity (typically a drawer destination) into a Fragment hosted by `NavigatorActivity`. Use it when the user mentions "navigator", `NavigatorActivity`, `NavigatorScreen`, or "convert activity to fragment". It covers registering the `NavigatorScreen`, the fragment and its layout, decoupling collaborators from `FileActivity`, rewiring every entry point with `NavigatorActivity.intent(...)`, DI and manifest cleanup, and migrating the screenshot test. +### Official Android Skills + +Google publishes official agent skills for Android development at https://github.com/android/skills (overview: https://developer.android.com/tools/agents/android-skills). Use them when a task matches their trigger and no project-specific skill above covers it. Install one with the Android CLI (`android skills add `) or read its `SKILL.md` directly from the repository. Project-specific skills and the rules in this file take precedence over the official skills. + +- **`r8-analyzer`** (https://github.com/android/skills/tree/main/performance/r8-analyzer) — Analyzes the R8 configuration and keep rules for redundant, overly broad, or library-subsumed rules. Use it when changing `app/src/main/keepRules/`, `app/proguard-rules.pro`, or the release `optimization {}` block. + ## General Guidance Every new file needs to get a SPDX header in the first rows according to this template. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0f701f218871..d1cff297c837 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,7 +20,7 @@ val shotTest = System.getenv("SHOT_TEST") == "true" val ciBuild = System.getenv("CI") == "true" val perfAnalysis = project.hasProperty("perfAnalysis") -val minify = project.hasProperty("minify") // shall be off by default +val minify = project.hasProperty("minify") plugins { alias(libs.plugins.android.application) @@ -143,20 +143,16 @@ android { flavorDimensions += "default" buildTypes { - fun com.android.build.api.dsl.BuildType.commonMinifyConfig() { - isMinifyEnabled = minify - isShrinkResources = minify - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } release { - commonMinifyConfig() + optimization { + enable = true + } buildConfigField("String", "NC_TEST_SERVER_DATA_STRING", "\"\"") } debug { - commonMinifyConfig() + optimization { + enable = minify + } enableUnitTestCoverage = project.hasProperty("coverage") enableAndroidTestCoverage = project.hasProperty("coverage") resConfigs("xxxhdpi") diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 3817ac49aca7..000000000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,24 +0,0 @@ -# Nextcloud - Android Client -# -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: AGPL-3.0-or-later - -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. - -# Silence missing-class warnings from optional transitive dependencies and Android/JDK API gaps. --dontwarn com.gemalto.jp2.JP2Decoder --dontwarn com.gemalto.jp2.JP2Encoder --dontwarn java.time.zone.ZoneRulesProvider --dontwarn org.joda.convert.ToString - -# Commons HttpClient is still used at runtime, so keep these packages from being stripped or renamed. --keep class org.apache.commons.httpclient.* { *; } --keep class org.apache.commons.httpclient.auth.** { *; } - -# Some libraries load .properties resources relative to their package. -# Keeping the package names preserves Class.getResourceAsStream(...) lookups after shrinking. --keeppackagenames org.apache.jackrabbit.webdav --keeppackagenames net.fortuna.ical4j --keep class org.apache.commons.httpclient.cookie.** { *; } diff --git a/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java b/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java index 709f368080d8..4901488e5611 100644 --- a/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java +++ b/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java @@ -15,8 +15,8 @@ import android.content.SharedPreferences; import android.content.res.Configuration; -import com.google.common.reflect.TypeToken; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import com.nextcloud.appReview.AppReviewShownModel; import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; diff --git a/app/src/main/keepRules/ezvcard.keep b/app/src/main/keepRules/ezvcard.keep new file mode 100644 index 000000000000..922c0e945b52 --- /dev/null +++ b/app/src/main/keepRules/ezvcard.keep @@ -0,0 +1,14 @@ +# Nextcloud - Android Client +# +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +# ez-vcard's CaseClasses look up the predefined parameter values (TelephoneType.CELL, ImageType.JPEG, ...) with +# Class.getFields() and create unknown values through these constructors with getDeclaredConstructor(). +# SupportedVersionsHelper reads the @SupportedVersions annotation from the same public static fields. +-keepclassmembers class * extends ezvcard.parameter.VCardParameter { + public static ; + (java.lang.String); + (java.lang.String, ezvcard.VCardVersion[]); + (java.lang.String, java.lang.String, java.lang.String); +} diff --git a/app/src/main/keepRules/gson-models.keep b/app/src/main/keepRules/gson-models.keep new file mode 100644 index 000000000000..544131db1987 --- /dev/null +++ b/app/src/main/keepRules/gson-models.keep @@ -0,0 +1,32 @@ +# Nextcloud - Android Client +# +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +# Gson maps JSON keys to field names by reflection. Gson's bundled rules only cover fields annotated with +# @SerializedName, which these models do not use. Their JSON is either persisted (SharedPreferences, database), +# exchanged with the server and other clients, or received from the server, so field names must stay as written. +# Keeping the constructors tells R8 full mode that Gson may instantiate the models; otherwise it treats models without +# a reachable constructor call as never created and turns casts to them into ClassCastExceptions. +-keepclassmembers class com.owncloud.android.authentication.AuthObject, + com.owncloud.android.authentication.Poll, + com.owncloud.android.authentication.LoginUrlInfo, + com.owncloud.android.authentication.EnforcedServer, + com.owncloud.android.datamodel.PushConfigurationState, + com.owncloud.android.datamodel.MediaFoldersModel, + com.owncloud.android.datamodel.DecryptedPushMessage, + com.nextcloud.client.jobs.LogEntry, + com.nextcloud.appReview.AppReviewShownModel, + com.nextcloud.client.NominatimClient$Companion$ReverseGeocodingResult, + com.nextcloud.model.OfflineOperationType$* { + !static !transient ; + (...); +} + +# End-to-end encryption metadata is signed and shared with the server and the other Nextcloud clients. +# EncryptionUtils serializes it with excludeFieldsWithModifiers(0), so every field is part of the format. +-keepclassmembers class com.owncloud.android.datamodel.e2e.**, + com.owncloud.android.datamodel.EncryptedFiledrop { + ; + (...); +} diff --git a/app/src/main/keepRules/ical4j.keep b/app/src/main/keepRules/ical4j.keep new file mode 100644 index 000000000000..9b44d005132a --- /dev/null +++ b/app/src/main/keepRules/ical4j.keep @@ -0,0 +1,22 @@ +# Nextcloud - Android Client +# +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +# ical4j instantiates the classes named in src/main/resources/ical4j.properties with Class.forName(), which R8 +# cannot see because it does not rewrite .properties files. +-keep class com.nextcloud.utils.ical.AndroidCompatTimeZoneRegistryFactory, + net.fortuna.ical4j.util.MapTimeZoneCache { + (); +} + +# TzHelper loads msTimezones* with a path relative to its own package. +-keeppackagenames net.fortuna.ical4j.transform.compliance + +# Android has no java.time.zone.ZoneRulesProvider. Only ical4j's default TimeZoneRegistryImpl reaches subclasses of it, +# and ical4j.properties replaces that registry with AndroidCompatTimeZoneRegistryFactory (kept above). +-dontwarn java.time.zone.ZoneRulesProvider + +# threeten-extra (an ical4j dependency) annotates Interval.toString() with an annotation from the optional joda-convert +# dependency. A missing annotation class is ignored at runtime. +-dontwarn org.joda.convert.ToString diff --git a/app/src/main/keepRules/legacy-http.keep b/app/src/main/keepRules/legacy-http.keep new file mode 100644 index 000000000000..464fc27609d4 --- /dev/null +++ b/app/src/main/keepRules/legacy-http.keep @@ -0,0 +1,19 @@ +# Nextcloud - Android Client +# +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +# Commons HttpClient 3.1 is compiled for Java 1.4, so AuthPolicy and CookiePolicy resolve the auth schemes, cookie +# specs and themselves through the class$("name") helper, whose strings R8 does not rewrite. The schemes and specs are +# then created with Class.newInstance(). +-keep class org.apache.commons.httpclient.auth.AuthPolicy +-keep class org.apache.commons.httpclient.cookie.CookiePolicy +-keep class * implements org.apache.commons.httpclient.auth.AuthScheme { + public (); +} +-keep class * implements org.apache.commons.httpclient.cookie.CookieSpec { + public (); +} + +# DavException loads statuscode.properties with a path relative to its own package. +-keeppackagenames org.apache.jackrabbit.webdav diff --git a/app/src/main/keepRules/nextcloud-library.keep b/app/src/main/keepRules/nextcloud-library.keep new file mode 100644 index 000000000000..173b81fd6bde --- /dev/null +++ b/app/src/main/keepRules/nextcloud-library.keep @@ -0,0 +1,21 @@ +# Nextcloud - Android Client +# +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +# com.github.nextcloud:android-library ships no consumer rules, but parses OCS responses with Gson into models that +# mostly lack @SerializedName, and the app persists several of them as JSON (DirectEditing, PredefinedStatus, +# ShareeUser, Tag, ImageDimension, GeoLocation). The models are spread across operation packages, so only fields and +# constructors are kept: unused classes are still removed and class names, methods and code are still optimized and +# obfuscated. Keeping the constructors tells R8 full mode that Gson may instantiate the models; otherwise it treats +# models without a reachable constructor call as never created and turns casts to them into ClassCastExceptions. +# Move this into the library's consumer rules once it publishes them. +-keepclassmembers class com.owncloud.android.lib.**, com.nextcloud.android.lib.** { + !static !transient ; + (...); +} + +# R8 full mode keeps the Signature attribute only on kept classes. Gson resolves the type argument of these generic +# OCS envelopes from it; without it the payload is parsed as a LinkedTreeMap and the cast to the model fails. +-keep,allowobfuscation,allowshrinking class com.owncloud.android.lib.ocs.ServerResponse, + com.owncloud.android.lib.ocs.OCSResponse diff --git a/app/src/main/keepRules/sso.keep b/app/src/main/keepRules/sso.keep new file mode 100644 index 000000000000..ac23929d3ce6 --- /dev/null +++ b/app/src/main/keepRules/sso.keep @@ -0,0 +1,17 @@ +# Nextcloud - Android Client +# +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +# Single Sign-On client apps send these classes through Java serialization, so class names, field names and +# serialVersionUID must match the Android-SingleSignOn library. They are never constructed in this app, so without +# this rule R8 treats them as uninstantiated and removes them. PlainHeader is also returned to clients as Gson JSON. +-keep class com.nextcloud.android.sso.aidl.NextcloudRequest, + com.nextcloud.android.sso.QueryParam, + com.nextcloud.android.sso.PlainHeader { + static final long serialVersionUID; + !static !transient ; + (...); + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); +} From a3d79bbeb950185e5cd981db807cde1ed2ece38a Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 6 Oct 2026 15:06:24 +0200 Subject: [PATCH 4/9] update agents md Signed-off-by: alperozturk96 --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 0001287a47a4..ffa9649fb165 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,7 +43,7 @@ Project-specific skills live in `./.claude/skills//`. Load a skill w Google publishes official agent skills for Android development at https://github.com/android/skills (overview: https://developer.android.com/tools/agents/android-skills). Use them when a task matches their trigger and no project-specific skill above covers it. Install one with the Android CLI (`android skills add `) or read its `SKILL.md` directly from the repository. Project-specific skills and the rules in this file take precedence over the official skills. -- **`r8-analyzer`** (https://github.com/android/skills/tree/main/performance/r8-analyzer) — Analyzes the R8 configuration and keep rules for redundant, overly broad, or library-subsumed rules. Use it when changing `app/src/main/keepRules/`, `app/proguard-rules.pro`, or the release `optimization {}` block. +- **`r8-analyzer`** (https://github.com/android/skills/tree/main/performance/r8-analyzer) — Analyzes the R8 configuration and keep rules for redundant, overly broad, or library-subsumed rules. Use it when changing the keep rules in `app/src/main/keepRules/` or the `optimization {}` blocks in `app/build.gradle.kts`. ## General Guidance From e3fe0d0d5cc43e56fa03b550505f4a4178d42986 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 6 Oct 2026 15:58:21 +0200 Subject: [PATCH 5/9] remove pminify Signed-off-by: alperozturk96 --- .github/workflows/assembleFlavors.yml | 3 ++- .github/workflows/unit-tests.yml | 2 +- AGENTS.md | 4 ++-- app/build.gradle.kts | 4 +--- scripts/runAllScreenshotCombinations | 2 -- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/assembleFlavors.yml b/.github/workflows/assembleFlavors.yml index 89ba5ac3ad41..a6ed7cc24dff 100644 --- a/.github/workflows/assembleFlavors.yml +++ b/.github/workflows/assembleFlavors.yml @@ -47,4 +47,5 @@ jobs: echo "kotlin.daemon.jvmargs=-Xmx3g -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m" echo "org.gradle.workers.max=2" } >> gradle.properties - ./gradlew ${{ matrix.tasks }} -Pminify + ./gradlew ${{ matrix.tasks }} + diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 7330ed6d05cb..11f934a1da3b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -40,7 +40,7 @@ jobs: uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0 - name: Run unit tests with coverage - run: ./gradlew jacocoTestGplayDebugUnitTest -Pminify + run: ./gradlew jacocoTestGplayDebugUnitTest - name: Upload failing results if: ${{ failure() }} diff --git a/AGENTS.md b/AGENTS.md index ffa9649fb165..63b73f2af614 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,9 +39,9 @@ Project-specific skills live in `./.claude/skills//`. Load a skill w - **`android-java-to-kotlin`** (`./.claude/skills/android-java-to-kotlin/SKILL.md`) — Completes a Java-to-Kotlin conversion in this Android app. Use it when finishing a conversion, when the user mentions "java to kotlin", "j2k", "convert java", or "make it idiomatic", or when a freshly IDE-converted `.kt` file needs cleanup. The workflow is two-person: the developer first runs the Android Studio converter (`Code > Convert Java File to Kotlin File`), then the agent drives the idiomatic second pass — fail-fast control flow, function decomposition, `lifecycleScope`/coroutines instead of Java threads, modern Android APIs, and project conventions (SPDX headers, no magic numbers, `@JvmStatic`). The conversion must preserve behaviour, and the agent must write a behaviour-locking test before declaring it done. - **`android-activity-to-navigator-fragment`** (`./.claude/skills/android-activity-to-navigator-fragment/SKILL.md`) — Converts a standalone Activity (typically a drawer destination) into a Fragment hosted by `NavigatorActivity`. Use it when the user mentions "navigator", `NavigatorActivity`, `NavigatorScreen`, or "convert activity to fragment". It covers registering the `NavigatorScreen`, the fragment and its layout, decoupling collaborators from `FileActivity`, rewiring every entry point with `NavigatorActivity.intent(...)`, DI and manifest cleanup, and migrating the screenshot test. -### Official Android Skills +### Android Skills -Google publishes official agent skills for Android development at https://github.com/android/skills (overview: https://developer.android.com/tools/agents/android-skills). Use them when a task matches their trigger and no project-specific skill above covers it. Install one with the Android CLI (`android skills add `) or read its `SKILL.md` directly from the repository. Project-specific skills and the rules in this file take precedence over the official skills. +Agent skills for Android development at https://github.com/android/skills (overview: https://developer.android.com/tools/agents/android-skills). Use them when a task matches their trigger and no project-specific skill above covers it. Install one with the Android CLI (`android skills add `) or read its `SKILL.md` directly from the repository. Project-specific skills and the rules in this file take precedence over the official skills. - **`r8-analyzer`** (https://github.com/android/skills/tree/main/performance/r8-analyzer) — Analyzes the R8 configuration and keep rules for redundant, overly broad, or library-subsumed rules. Use it when changing the keep rules in `app/src/main/keepRules/` or the `optimization {}` blocks in `app/build.gradle.kts`. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d1cff297c837..4691d9356031 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,8 +20,6 @@ val shotTest = System.getenv("SHOT_TEST") == "true" val ciBuild = System.getenv("CI") == "true" val perfAnalysis = project.hasProperty("perfAnalysis") -val minify = project.hasProperty("minify") - plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) @@ -151,7 +149,7 @@ android { } debug { optimization { - enable = minify + enable = true } enableUnitTestCoverage = project.hasProperty("coverage") enableAndroidTestCoverage = project.hasProperty("coverage") diff --git a/scripts/runAllScreenshotCombinations b/scripts/runAllScreenshotCombinations index a0d7caf99950..6e208d20eed0 100755 --- a/scripts/runAllScreenshotCombinations +++ b/scripts/runAllScreenshotCombinations @@ -30,7 +30,6 @@ do if [[ $1 = "noCI" ]]; then ./gradlew --console plain genericDebugExecuteScreenshotTests \ $record \ - -Pminify \ -Pscreenshot=true \ -Pandroid.testInstrumentationRunnerArguments.annotation=com.owncloud.android.utils.ScreenshotTest \ -Pandroid.testInstrumentationRunnerArguments.COLOR="$color" \ @@ -44,7 +43,6 @@ do else ./gradlew --console plain genericDebugExecuteScreenshotTests \ $record \ - -Pminify \ -Pandroid.testInstrumentationRunnerArguments.annotation=com.owncloud.android.utils.ScreenshotTest \ -Pandroid.testInstrumentationRunnerArguments.COLOR="$color" \ -Pandroid.testInstrumentationRunnerArguments.DARKMODE="$darkMode" /dev/null \ From a559af213d8851e931513aed400e0853640b3d0e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 6 Oct 2026 16:04:51 +0200 Subject: [PATCH 6/9] fix slide behaviour crash when going back from settings Signed-off-by: alperozturk96 --- .../main/java/com/nextcloud/utils/extensions/ViewExtensions.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt index d4d79fbd1c37..45ec63cbe09b 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/ViewExtensions.kt @@ -17,6 +17,7 @@ import android.view.ViewOutlineProvider import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.doOnLayout import androidx.core.view.updatePadding import com.google.android.material.behavior.HideViewOnScrollBehavior @@ -81,7 +82,7 @@ fun View?.slideHideBottomBehavior(visible: Boolean) { if (params?.behavior !is HideViewOnScrollBehavior<*>) return val behavior = HideViewOnScrollBehavior.from(view) - view.post { + view.doOnLayout { if (visible) behavior.slideIn(view) else behavior.slideOut(view) } } From a1719322897bfe1fb9b4ad19902a084e8c36469e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 6 Oct 2026 16:19:32 +0200 Subject: [PATCH 7/9] remove pmnifiy Signed-off-by: alperozturk96 --- scripts/runCombinedTest.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/runCombinedTest.sh b/scripts/runCombinedTest.sh index 29c12989b0a6..86ecf0cc6763 100755 --- a/scripts/runCombinedTest.sh +++ b/scripts/runCombinedTest.sh @@ -24,13 +24,12 @@ scripts/deleteOldComments.sh "$BRANCH" "IT" "$DRONE_PULL_REQUEST" scripts/wait_for_emulator.sh || exit 1 -./gradlew installGplayDebugAndroidTest -Pminify +./gradlew installGplayDebugAndroidTest gradle_arguments=( -Pcoverage -Pandroid.testInstrumentationRunnerArguments.notAnnotation=com.owncloud.android.utils.ScreenshotTest -Pandroid.testInstrumentationRunnerArguments.filter=com.nextcloud.test.FlakyTestFilter,com.nextcloud.test.ServerVersionFilter - -Pminify ) if [[ "$BRANCH" =~ ^stable([0-9]+)$ ]]; then From 0f914a674aebfa78ce5fcc0f102989deda939637 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 7 Oct 2026 12:04:26 +0200 Subject: [PATCH 8/9] update license header and remove comments Signed-off-by: alperozturk96 --- app/src/main/keepRules/ezvcard.keep | 5 +---- app/src/main/keepRules/gson-models.keep | 9 +-------- app/src/main/keepRules/ical4j.keep | 10 +--------- app/src/main/keepRules/legacy-http.keep | 6 +----- app/src/main/keepRules/nextcloud-library.keep | 11 +---------- app/src/main/keepRules/sso.keep | 5 +---- 6 files changed, 6 insertions(+), 40 deletions(-) diff --git a/app/src/main/keepRules/ezvcard.keep b/app/src/main/keepRules/ezvcard.keep index 922c0e945b52..8cb9e415917b 100644 --- a/app/src/main/keepRules/ezvcard.keep +++ b/app/src/main/keepRules/ezvcard.keep @@ -1,11 +1,8 @@ # Nextcloud - Android Client # -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2026 Alper Ozturk # SPDX-License-Identifier: AGPL-3.0-or-later -# ez-vcard's CaseClasses look up the predefined parameter values (TelephoneType.CELL, ImageType.JPEG, ...) with -# Class.getFields() and create unknown values through these constructors with getDeclaredConstructor(). -# SupportedVersionsHelper reads the @SupportedVersions annotation from the same public static fields. -keepclassmembers class * extends ezvcard.parameter.VCardParameter { public static ; (java.lang.String); diff --git a/app/src/main/keepRules/gson-models.keep b/app/src/main/keepRules/gson-models.keep index 544131db1987..4f53fdc15ac3 100644 --- a/app/src/main/keepRules/gson-models.keep +++ b/app/src/main/keepRules/gson-models.keep @@ -1,13 +1,8 @@ # Nextcloud - Android Client # -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2026 Alper Ozturk # SPDX-License-Identifier: AGPL-3.0-or-later -# Gson maps JSON keys to field names by reflection. Gson's bundled rules only cover fields annotated with -# @SerializedName, which these models do not use. Their JSON is either persisted (SharedPreferences, database), -# exchanged with the server and other clients, or received from the server, so field names must stay as written. -# Keeping the constructors tells R8 full mode that Gson may instantiate the models; otherwise it treats models without -# a reachable constructor call as never created and turns casts to them into ClassCastExceptions. -keepclassmembers class com.owncloud.android.authentication.AuthObject, com.owncloud.android.authentication.Poll, com.owncloud.android.authentication.LoginUrlInfo, @@ -23,8 +18,6 @@ (...); } -# End-to-end encryption metadata is signed and shared with the server and the other Nextcloud clients. -# EncryptionUtils serializes it with excludeFieldsWithModifiers(0), so every field is part of the format. -keepclassmembers class com.owncloud.android.datamodel.e2e.**, com.owncloud.android.datamodel.EncryptedFiledrop { ; diff --git a/app/src/main/keepRules/ical4j.keep b/app/src/main/keepRules/ical4j.keep index 9b44d005132a..a21977a36871 100644 --- a/app/src/main/keepRules/ical4j.keep +++ b/app/src/main/keepRules/ical4j.keep @@ -1,22 +1,14 @@ # Nextcloud - Android Client # -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2026 Alper Ozturk # SPDX-License-Identifier: AGPL-3.0-or-later -# ical4j instantiates the classes named in src/main/resources/ical4j.properties with Class.forName(), which R8 -# cannot see because it does not rewrite .properties files. -keep class com.nextcloud.utils.ical.AndroidCompatTimeZoneRegistryFactory, net.fortuna.ical4j.util.MapTimeZoneCache { (); } -# TzHelper loads msTimezones* with a path relative to its own package. -keeppackagenames net.fortuna.ical4j.transform.compliance -# Android has no java.time.zone.ZoneRulesProvider. Only ical4j's default TimeZoneRegistryImpl reaches subclasses of it, -# and ical4j.properties replaces that registry with AndroidCompatTimeZoneRegistryFactory (kept above). -dontwarn java.time.zone.ZoneRulesProvider - -# threeten-extra (an ical4j dependency) annotates Interval.toString() with an annotation from the optional joda-convert -# dependency. A missing annotation class is ignored at runtime. -dontwarn org.joda.convert.ToString diff --git a/app/src/main/keepRules/legacy-http.keep b/app/src/main/keepRules/legacy-http.keep index 464fc27609d4..905cdc3a9026 100644 --- a/app/src/main/keepRules/legacy-http.keep +++ b/app/src/main/keepRules/legacy-http.keep @@ -1,11 +1,8 @@ # Nextcloud - Android Client # -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2026 Alper Ozturk # SPDX-License-Identifier: AGPL-3.0-or-later -# Commons HttpClient 3.1 is compiled for Java 1.4, so AuthPolicy and CookiePolicy resolve the auth schemes, cookie -# specs and themselves through the class$("name") helper, whose strings R8 does not rewrite. The schemes and specs are -# then created with Class.newInstance(). -keep class org.apache.commons.httpclient.auth.AuthPolicy -keep class org.apache.commons.httpclient.cookie.CookiePolicy -keep class * implements org.apache.commons.httpclient.auth.AuthScheme { @@ -15,5 +12,4 @@ public (); } -# DavException loads statuscode.properties with a path relative to its own package. -keeppackagenames org.apache.jackrabbit.webdav diff --git a/app/src/main/keepRules/nextcloud-library.keep b/app/src/main/keepRules/nextcloud-library.keep index 173b81fd6bde..ac61798c6e7d 100644 --- a/app/src/main/keepRules/nextcloud-library.keep +++ b/app/src/main/keepRules/nextcloud-library.keep @@ -1,21 +1,12 @@ # Nextcloud - Android Client # -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2026 Alper Ozturk # SPDX-License-Identifier: AGPL-3.0-or-later -# com.github.nextcloud:android-library ships no consumer rules, but parses OCS responses with Gson into models that -# mostly lack @SerializedName, and the app persists several of them as JSON (DirectEditing, PredefinedStatus, -# ShareeUser, Tag, ImageDimension, GeoLocation). The models are spread across operation packages, so only fields and -# constructors are kept: unused classes are still removed and class names, methods and code are still optimized and -# obfuscated. Keeping the constructors tells R8 full mode that Gson may instantiate the models; otherwise it treats -# models without a reachable constructor call as never created and turns casts to them into ClassCastExceptions. -# Move this into the library's consumer rules once it publishes them. -keepclassmembers class com.owncloud.android.lib.**, com.nextcloud.android.lib.** { !static !transient ; (...); } -# R8 full mode keeps the Signature attribute only on kept classes. Gson resolves the type argument of these generic -# OCS envelopes from it; without it the payload is parsed as a LinkedTreeMap and the cast to the model fails. -keep,allowobfuscation,allowshrinking class com.owncloud.android.lib.ocs.ServerResponse, com.owncloud.android.lib.ocs.OCSResponse diff --git a/app/src/main/keepRules/sso.keep b/app/src/main/keepRules/sso.keep index ac23929d3ce6..e9e04379d0aa 100644 --- a/app/src/main/keepRules/sso.keep +++ b/app/src/main/keepRules/sso.keep @@ -1,11 +1,8 @@ # Nextcloud - Android Client # -# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-FileCopyrightText: 2026 Alper Ozturk # SPDX-License-Identifier: AGPL-3.0-or-later -# Single Sign-On client apps send these classes through Java serialization, so class names, field names and -# serialVersionUID must match the Android-SingleSignOn library. They are never constructed in this app, so without -# this rule R8 treats them as uninstantiated and removes them. PlainHeader is also returned to clients as Gson JSON. -keep class com.nextcloud.android.sso.aidl.NextcloudRequest, com.nextcloud.android.sso.QueryParam, com.nextcloud.android.sso.PlainHeader { From 4bbd08f80f3daa2428cbdfdf616703b9196fef77 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 7 Oct 2026 12:36:27 +0200 Subject: [PATCH 9/9] fix garm Signed-off-by: alperozturk96 --- app/build.gradle.kts | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4691d9356031..f544da971f56 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -148,9 +148,6 @@ android { buildConfigField("String", "NC_TEST_SERVER_DATA_STRING", "\"\"") } debug { - optimization { - enable = true - } enableUnitTestCoverage = project.hasProperty("coverage") enableAndroidTestCoverage = project.hasProperty("coverage") resConfigs("xxxhdpi")