Visitar URL original
chore: add openspec change for DEX code optimization · owncloud/android@f7b313a · GitHub
Skip to content

Commit f7b313a

Browse files
committed
chore: add openspec change for DEX code optimization
Signed-off-by: Jesus Recio <jrecio@izertis.com>
1 parent 45c76e8 commit f7b313a

5 files changed

Lines changed: 215 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-10-22
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
## Context
2+
3+
See proposal.md — Why for motivation. The `owncloudApp/build.gradle` release build type currently has no `minifyEnabled` setting (defaults to `false`). No `proguard-rules.pro` exists in `owncloudApp/`. A legacy `proguard.cfg` exists at the repo root but is not referenced by any build script. AGP 9.x uses R8 by default; no explicit R8 toolchain switch is needed.
4+
5+
## Goals / Non-Goals
6+
7+
**Goals:**
8+
- Enable R8 code shrinking for `owncloudApp` release builds only.
9+
- Author keep rules sufficient to prevent runtime breakage from missing classes.
10+
11+
**Non-Goals:**
12+
- Enabling minification in library modules or debug builds.
13+
- Adding obfuscation suppression (`-dontobfuscate`) — obfuscation stays on.
14+
- Integrating a crash-reporting SDK or automating mapping file upload.
15+
16+
## Decisions
17+
18+
### D1: Use `getDefaultProguardFile('proguard-android-optimize.txt')` as the base rules file
19+
20+
**Decision:** Reference `proguard-android-optimize.txt` (not `proguard-android.txt`) as the AGP-provided base rules file.
21+
22+
**Rationale:** `proguard-android-optimize.txt` enables code-level optimizations (constant propagation, dead code removal across methods) that `proguard-android.txt` does not. This maximises APK size reduction and aligns with Google's current recommendation for new projects.
23+
24+
**Alternative considered:** `proguard-android.txt` — less aggressive, larger output, not needed given we are starting from scratch with R8.
25+
26+
### D2: Use a single `owncloudApp/proguard-rules.pro` file for app-specific keep rules
27+
28+
**Decision:** All custom keep rules live in one file at `owncloudApp/proguard-rules.pro`, referenced alongside the AGP default.
29+
30+
**Rationale:** Library consumer rules (Room, Kotlin Coroutines, OkHttp, Moshi, Koin) are already bundled with each library's AAR and applied automatically by R8. Only app-level classes that R8 cannot infer are instantiated by name (WorkManager workers, classes loaded via reflection) need explicit keep rules. A single file is easy to audit and extend.
31+
32+
**Alternative considered:** Splitting rules per concern into multiple `.pro` files — unnecessary complexity for the volume of custom rules needed here.
33+
34+
### D3: Keep rules strategy by class category
35+
36+
| Category | Rule strategy | Rationale |
37+
|---|---|---|
38+
| `CoroutineWorker` subclasses in `com.owncloud.android.workers` | `-keep class com.owncloud.android.workers.** { public <init>(...); }` | WorkManager instantiates workers by class name from the `WorkRequest`; renaming breaks lookup |
39+
| Moshi `@JsonClass`-annotated models in `owncloudComLibrary` | `-keep @com.squareup.moshi.JsonClass class * { *; }` | KSP generates adapters at compile time, but the annotated model fields must not be renamed or JSON field mapping breaks |
40+
| Room entities and DAOs | Already covered by Room's bundled consumer rules — verify after first build by inspecting `configuration.txt` | Room ships its own `-keep` rules in its AAR |
41+
| Koin wiring | No keep rules needed | Koin resolves dependencies via reified type parameters, not reflection on class names |
42+
| All other classes | Default R8 behavior (shrink + obfuscate) | No known issues; the `configuration.txt` dump after the first build is the safety net |
43+
44+
### D4: Verify merged rules after the first successful release build
45+
46+
**Decision:** Inspect `owncloudApp/build/outputs/mapping/originalRelease/configuration.txt` after the first successful `assembleOriginalRelease` to confirm all consumer rules were applied and spot any unexpected gaps before opening the PR.
47+
48+
**Rationale:** R8 merges rules cumulatively (AGP default + `proguard-rules.pro` + library consumer rules). The merged dump is the ground truth; reviewing it catches over-keeping and under-keeping early.
49+
50+
## Risks / Trade-offs
51+
52+
- **[Risk] Keep rules are incomplete** → Mitigation: Run the release APK on a device/emulator and exercise login, file listing, upload, download, and background sync before opening the PR. Missing keep rules typically surface as `ClassNotFoundException` or Moshi deserialization errors at runtime, not at build time.
53+
- **[Risk] R8 removes code that is used only via reflection (e.g. custom View classes inflated from XML)** → Mitigation: Review the R8 usage report (`usage.txt` in the mapping output directory) and add `@Keep` annotations or explicit rules for any incorrectly removed class.
54+
- **[Risk] Detekt flags new annotations or changed constructs** → Mitigation: Run `./gradlew detekt` locally after every change to the keep rules file and before pushing.
55+
56+
## Migration Plan
57+
58+
1. Add `minifyEnabled true` and `proguardFiles` to the `release` build type in `owncloudApp/build.gradle`.
59+
2. Create `owncloudApp/proguard-rules.pro` with keep rules per D3.
60+
3. Run `./gradlew assembleOriginalRelease` and resolve any build errors.
61+
4. Inspect `configuration.txt` (D4) and iterate on `proguard-rules.pro` as needed.
62+
5. Install the release APK on a device/emulator and smoke-test (login, file listing, upload, download, background sync).
63+
6. Run `./gradlew testDebugUnitTest testMdmDebugUnitTest` to confirm unit tests pass.
64+
7. Run `./gradlew detekt` to confirm zero issues.
65+
66+
**Rollback:** Revert `owncloudApp/build.gradle` to remove the `minifyEnabled` and `proguardFiles` lines. Delete `owncloudApp/proguard-rules.pro`. No database migrations or server-side changes are involved.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Why
2+
3+
The release APK published on Google Play has DEX code optimization disabled because `minifyEnabled` is not set in `owncloudApp/build.gradle`. Google Play Console flags this in Android Vitals and requires it to be fixed by February 2027. Enabling R8 also reduces APK size.
4+
5+
## What Changes
6+
7+
- Enable `minifyEnabled true` in the `release` build type of `owncloudApp/build.gradle`.
8+
- Add `proguardFiles` referencing `getDefaultProguardFile('proguard-android-optimize.txt')` and a new `owncloudApp/proguard-rules.pro`.
9+
- Create `owncloudApp/proguard-rules.pro` with keep rules for: `CoroutineWorker` subclasses in `com.owncloud.android.workers`, Moshi `@JsonClass`-annotated response models in `owncloudComLibrary`, and any classes loaded by reflection or the Android framework.
10+
- Debug builds remain unaffected (`minifyEnabled false`).
11+
- Library modules (`owncloudData`, `owncloudDomain`, `owncloudComLibrary`) are not changed.
12+
13+
## Capabilities
14+
15+
### New Capabilities
16+
17+
- `r8-optimisation`: Release build configuration requirements — minification enabled, keep rules covering Workers, Moshi models, and reflection-loaded classes.
18+
19+
### Modified Capabilities
20+
21+
None — no existing spec-level behavior changes. The app's observable user-facing functionality is unchanged.
22+
23+
## Impact
24+
25+
- **`owncloudApp/build.gradle`**: `release` build type gains `minifyEnabled true` and `proguardFiles`.
26+
- **`owncloudApp/proguard-rules.pro`** (new file): keep rules for Workers, Moshi models, and reflection-loaded classes.
27+
- No new runtime dependencies introduced.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## Purpose
2+
3+
Defines the build requirements for R8 code shrinking and optimization in release builds of the ownCloud Android app, ensuring APKs pass Google Play's DEX optimization check.
4+
5+
## ADDED Requirements
6+
7+
### Requirement: Release builds apply R8 code shrinking
8+
9+
The `owncloudApp` release build type SHALL have `minifyEnabled true` set, causing the AGP/R8 toolchain to shrink and optimize the DEX code.
10+
11+
#### Scenario: Release APK is produced with R8 enabled
12+
13+
- **WHEN** `./gradlew assembleOriginalRelease` is executed
14+
- **THEN** the build completes without errors and produces a valid, installable APK at `owncloudApp/build/outputs/apk/original/release/`
15+
16+
#### Scenario: Debug builds remain unminified
17+
18+
- **WHEN** `./gradlew assembleOriginalDebug` is executed
19+
- **THEN** `minifyEnabled` is false for the debug build type and no shrinking is applied
20+
21+
### Requirement: Keep rules preserve reflection-loaded and framework-instantiated classes
22+
23+
The ProGuard/R8 rules file SHALL include keep rules that prevent shrinking or renaming of: all `CoroutineWorker` subclasses in `com.owncloud.android.workers`, Moshi `@JsonClass`-annotated response model classes in `owncloudComLibrary`, and any other classes instantiated by name via reflection or the Android framework.
24+
25+
#### Scenario: WorkManager workers are instantiated correctly at runtime
26+
27+
- **WHEN** a background sync or upload WorkRequest is enqueued and executed in a release build
28+
- **THEN** WorkManager successfully instantiates the worker class by its registered name with no `ClassNotFoundException`
29+
30+
#### Scenario: Moshi JSON deserialization succeeds in release builds
31+
32+
- **WHEN** the app deserializes a JSON response from the ownCloud server in a release build
33+
- **THEN** all annotated fields are correctly mapped and no deserialization errors occur due to renamed fields
34+
35+
### Requirement: Library modules are not affected
36+
37+
The `owncloudComLibrary`, `owncloudData`, and `owncloudDomain` library modules SHALL keep `minifyEnabled false`; only `owncloudApp` is changed.
38+
39+
#### Scenario: Library module builds are unchanged
40+
41+
- **WHEN** any library module is built independently
42+
- **THEN** its build configuration is identical to before this change (no minification applied)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
## 1. Build Configuration
2+
3+
- [ ] 1.1 Add `minifyEnabled true` to the `release` build type in `owncloudApp/build.gradle`
4+
- [ ] 1.2 Add `proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'` to the `release` build type in `owncloudApp/build.gradle`
5+
- [ ] 1.3 Verify that the `debug` build type in `owncloudApp/build.gradle` has no `minifyEnabled` change (remains false by default)
6+
7+
## 2. ProGuard Rules
8+
9+
- [ ] 2.1 Create `owncloudApp/proguard-rules.pro` with keep rules for all `CoroutineWorker` subclasses in `com.owncloud.android.workers` (9 workers: `OldLogsCollectorWorker`, `AutomaticUploadsWorker`, `AvailableOfflinePeriodicWorker`, `DownloadFileWorker`, `RemoveLocallyFilesWithLastUsageOlderThanGivenTimeWorker`, `AccountDiscoveryWorker`, `UploadFileFromFileSystemWorker`, `RemoveSourceFileWorker`, `UploadFileFromContentUriWorker`)
10+
- [ ] 2.2 Add keep rules to `owncloudApp/proguard-rules.pro` for all Moshi `@JsonClass`-annotated response model classes in `owncloudComLibrary` (covering: `CommonOcsResponse`, `ShareeResponse`, `ShareItem`, `TokenResponse`, `ClientRegistrationResponse`, `OIDCDiscoveryResponse`, `WebFingerResponse`, `AppRegistryResponse`, `SpacesResponse`, `RoleResponse`, `CapabilityResponse`, `UserInfoResponse`, `MembersResponse`, and related)
11+
- [ ] 2.3 Add a class-level keep rule for any other classes instantiated by name via the Android framework (e.g. custom `View` subclasses inflated from XML, if any are identified)
12+
13+
## 3. First Build and Rule Verification
14+
15+
- [ ] 3.1 Run `./gradlew assembleOriginalRelease` and resolve any build errors
16+
- [ ] 3.2 Inspect `owncloudApp/build/outputs/mapping/originalRelease/configuration.txt` to verify that Room consumer rules and Coroutines rules were applied automatically, and that all custom keep rules appear in the merged output
17+
- [ ] 3.3 Inspect `owncloudApp/build/outputs/mapping/originalRelease/usage.txt` to check for any incorrectly removed classes and add additional keep rules if needed
18+
- [ ] 3.4 Confirm mapping file is present at `owncloudApp/build/outputs/mapping/originalRelease/mapping.txt`
19+
20+
## 4. Verification
21+
22+
- [ ] 4.1 Run `./gradlew testDebugUnitTest testMdmDebugUnitTest --continue` and confirm all unit tests pass
23+
- [ ] 4.2 Run `./gradlew detekt` and confirm zero issues
24+
- [ ] 4.3 Install the release APK on a device or emulator and perform the smoke test: login, file listing, upload, download
25+
- [ ] 4.4 Trigger a background sync (automatic upload) and confirm it completes without a `ClassNotFoundException` from WorkManager
26+
27+
## 5. Changelog
28+
29+
- [ ] 5.1 Create `changelog/unreleased/XXXX` (replace `XXXX` with the PR number once known) with the following content:
30+
31+
```
32+
Enhancement: Enable R8 code shrinking for release builds
33+
34+
R8 code shrinking has been enabled for release builds. The APK is now smaller and passes Google Play's DEX optimisation check.
35+
36+
https://github.com/owncloud/android/issues/5029
37+
https://github.com/owncloud/android/pull/XXXX
38+
```
39+
40+
---
41+
42+
## QA
43+
44+
### Test Plan
45+
46+
| Test name | Steps to execute | Expected result | Result | Comments |
47+
|---|---|---|---|---|
48+
| Release build compiles | Run `./gradlew assembleOriginalRelease` | Build succeeds, APK produced | | |
49+
| Mapping file generated | Check `owncloudApp/build/outputs/mapping/originalRelease/mapping.txt` after release build | File exists and is non-empty | | |
50+
| Debug build unaffected | Run `./gradlew assembleOriginalDebug` | Build succeeds, no minification applied | | |
51+
| Unit tests pass | Run `./gradlew testDebugUnitTest testMdmDebugUnitTest` | All tests green | | |
52+
| Detekt zero issues | Run `./gradlew detekt` | Zero issues reported | | |
53+
| Login works in release APK | Install release APK, open app, log in to an ownCloud server | Login completes, files listed | | |
54+
| File listing works | Navigate through folders in release APK | Folders and files display correctly | | |
55+
| File upload works | Upload a file from the release APK | File appears on server | | |
56+
| File download works | Download a file in the release APK | File saved locally and opens correctly | | |
57+
| Background sync works | Trigger automatic upload via release APK | Upload worker completes with no `ClassNotFoundException` | | |
58+
| Moshi deserialization works | Perform any server request returning JSON (e.g. capabilities, spaces) in release APK | Response parsed correctly, no deserialization errors | | |
59+
60+
### Gherkin Scenario Proposal (E2E suite)
61+
62+
```gherkin
63+
Feature: Release build integrity with R8 optimisation
64+
65+
Scenario: User can log in and list files with a release build
66+
Given the ownCloud Android app is installed from a release APK with R8 optimisation enabled
67+
And a valid ownCloud server is accessible
68+
When the user enters their credentials and taps the login button
69+
Then the app navigates to the file list screen
70+
And files and folders from the server are displayed correctly
71+
72+
Scenario: Background worker completes without reflection errors
73+
Given the ownCloud Android app is running as a release build
74+
And automatic uploads are enabled for a local folder
75+
When a new file is added to the watched folder
76+
Then the AutomaticUploadsWorker is enqueued and executed by WorkManager
77+
And the file is uploaded to the ownCloud server without a ClassNotFoundException
78+
```

0 commit comments

Comments
 (0)