Skip to content

Commit a4865b5

Browse files
Fix: Enable universal APK with riscv64 and fix BuildConfig.VERSION_CODE
1 parent ae351a3 commit a4865b5

3 files changed

Lines changed: 86 additions & 62 deletions

File tree

.github/copilot-instructions.md

Lines changed: 62 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,67 @@
11
# Copilot instructions for calsync
22

3-
This repo is an Android (Kotlin) app that listens to system notifications, parses Chinese time expressions, and writes calendar events. Keep responses concise and follow the codebase’s patterns below.
4-
5-
## Architecture and data flow
6-
- Entry points
7-
- `NotificationMonitorService` (NotificationListenerService): receives notifications, extracts title/content, and invokes `NotificationProcessor.process(...)` on a background dispatcher.
8-
- `KeepAliveService` (foreground service, type specialUse): optional keep-alive, shows ongoing notification; controlled by `SettingsStore.isKeepAliveEnabled`.
9-
- `CalSyncApp` (Application): window inset handling, crash capture + deferred broadcast via `CrashNotifierReceiver`.
10-
- Processing pipeline (single source of truth)
11-
1) Keyword filter from `SettingsStore.getKeywords()` and optional app filter from `SettingsStore.getSelectedSourceAppPkgs()`.
12-
2) Sentence extraction with `DateTimeParser.extractAllSentencesContainingDate(...)`.
13-
3) Date-time parsing: prefer Rule-based parser with context and baseMillis; fallback to `TimeNLPAdapter` iff `SettingsStore.isTimeNLPEnabled`.
14-
4) Event insertion via `CalendarHelper.insertEvent(...)`.
15-
5) Confirmation via `NotificationUtils.sendEventCreated(...)` and in-app broadcast `NotificationUtils.ACTION_EVENT_CREATED`.
16-
- Parsing contracts
17-
- Public: `DateTimeParser.parseDateTime(sentence)` and `DateTimeParser.parseDateTime(context, sentence, baseMillis)` returning `ParseResult(startMillis, endMillis?, title?, location?)`.
18-
- `TimeNLPAdapter.parse(text, baseMillis?) -> List<ParseSlot>`; includes small LRU cache and merges date-only/time-only units and ranges like "周五3点到5点".
19-
- Relative time support like “3天后”“1个半小时后”“还剩X天/小时/分钟/秒” is handled in `DateTimeParser`.
20-
21-
## Project-specific conventions
22-
- Language/locale: Simplified Chinese-first UX; tests freeze a base time when checking parsing.
23-
- Prefer-future behavior is tri-state via `SettingsStore.getPreferFutureOption()`:
24-
- 0=Auto (heuristics), 1=Prefer future, 2=Disable (don’t roll forward).
25-
- Default times: missing explicit time defaults to 09:00; evening/pm tokens map to 19:00 where appropriate.
26-
- Title/location extraction uses `JiebaWrapper` and heuristics; avoid over-engineering titles—keep short and meaningful.
27-
- Notification channels consolidated: confirmations use `NotificationUtils.CHANNEL_CONFIRM`; errors use `CHANNEL_ERROR`.
28-
- All confirmation notifications include a delete-action handled by `EventActionReceiver` and a deep link to the created event.
29-
- Do not log or toast full notification contents in production paths; rely on `NotificationCache` for brief summaries.
30-
31-
## Key files to learn by example
32-
- Parsing: `DateTimeParser.kt`, `TimeNLPAdapter.kt`, `timenlp/internal/*` (regex-based normalizer), `JiebaWrapper.kt`.
33-
- Notification flow: `NotificationMonitorService.kt`, `NotificationHelper.kt`, `NotificationUtils.kt`, `NotificationCache.kt`.
34-
- Calendar I/O: `CalendarHelper.kt`, `EventActionReceiver.kt`.
35-
- Settings: `SettingsStore.kt` (keywords, selected calendars, app allowlist, prefer-future, TimeNLP toggle, relative words).
36-
37-
## Build, run, test
38-
- JDK 21, AGP 8.13, Kotlin 2.0.21; module `:app` targets minSdk 23, targetSdk 36; Kotlin/JVM target 11.
39-
- Local build examples (PowerShell on Windows):
40-
- Debug APK: `./gradlew assembleDebug`
41-
- Release APK: `./gradlew assembleRelease`
42-
- Unit tests: `./gradlew :app:testDebugUnitTest --no-daemon --info`
43-
- If OOM, set `GRADLE_OPTS=-Xmx3g` for the process.
44-
- CI: see `.github/workflows/android-build-release.yml`; it builds debug/release and attaches APKs to a GitHub Release.
45-
46-
## Safe changes and patterns for agents
47-
- When adding new parse rules:
48-
- Extend `DateTimeParser` first; add corresponding unit tests in `app/src/test/java/top/stevezmt/calsync/` with frozen base calendars (see `DateTimeParserTest.kt`).
49-
- Only fall back to `TimeNLPAdapter` after rule-based attempts.
50-
- When touching notification flow:
51-
- Keep work on background dispatcher; don’t block `NotificationListenerService` callbacks.
52-
- Update channels via `NotificationUtils.ensureChannels`; use `safeNotify` to handle T+ permission.
53-
- Calendar writes:
54-
- Use `SettingsStore.getSelectedCalendarId` when set; otherwise fallback to first visible calendar; always set timezone.
55-
- Respect privacy:
56-
- App has no INTERNET permission; avoid adding network code. Keep logs minimal and avoid leaking sensitive notification text.
3+
Concise guide for AI agents: focus on the notification → parse → calendar flow and follow project conventions (privacy, tests, and background dispatching).
4+
5+
## Big picture
6+
- Flow: NotificationListener (`NotificationMonitorService`) → `NotificationProcessor` → sentence extraction (`DateTimeParser.extractAllSentencesContainingDate`) → parsing (`DateTimeParser` rule set, fallback `TimeNLPAdapter` when enabled) → calendar write (`CalendarHelper.insertEvent`) → user confirmation (`NotificationUtils.sendEventCreated`).
7+
- Entry points you’ll touch most: `NotificationMonitorService`, `NotificationProcessor`, `DateTimeParser`, `TimeNLPAdapter`, `CalendarHelper`, `NotificationUtils`.
8+
9+
## Key conventions & behaviors
10+
- Parsing API: `DateTimeParser.parseDateTime(sentence)` and `DateTimeParser.parseDateTime(context, sentence, baseMillis)` → returns `ParseResult(startMillis, endMillis?, title?, location?)`.
11+
- Prefer-future: `SettingsStore.getPreferFutureOption()` (0=auto, 1=prefer future, 2=disable).
12+
- Defaults: missing time → 09:00; evening tokens map to 19:00; relative expressions ("3天后", "还剩X小时") supported.
13+
- Title/location: lightweight extraction via `JiebaWrapper`; keep titles short and heuristic-driven.
14+
- Privacy: app has NO INTERNET permission — do not add network code or log full notification texts in production.
15+
16+
## Tests & developer workflow
17+
- Environment: JDK 21, AGP 8.13, Kotlin 2.0.21; run via Gradle wrapper.
18+
- Common commands:
19+
- ./gradlew assembleDebug
20+
- ./gradlew assembleRelease
21+
- ./gradlew :app:testDebugUnitTest --no-daemon --info
22+
- ./gradlew connectedDebugAndroidTest
23+
- If OOM: set `GRADLE_OPTS=-Xmx3g`.
24+
- Lint: `./gradlew :app:lintDebug` and view `app/build/reports/lint-results-debug.html`.
25+
for FOSS flavor:
26+
- ./gradlew assembleFossDebug
27+
- ./gradlew :app:testFossDebugUnitTest --no-daemon --info
28+
29+
- Testing pattern: parser rules require unit tests with a frozen base time (see `app/src/test/*/DateTimeParserTest*.kt`). Use `DateTimeParser.parseDateTime(..., baseMillis)` or test helpers like `parseWithBase`.
30+
- To run a single test class: `./gradlew :app:testDebugUnitTest --tests "top.stevezmt.calsync.DateTimeParserTest*"`.
31+
32+
## Safe change patterns for agents
33+
- Parser changes: extend `DateTimeParser` first, add targeted unit tests under `app/src/test/java/top/stevezmt/calsync/`, assert start/end/timezone behavior with a fixed base. Only use `TimeNLPAdapter` as fallback and guard with `SettingsStore.isTimeNLPEnabled()`.
34+
- Notification flow: do heavy work on background dispatcher; never block `NotificationListenerService` callbacks. Use `NotificationUtils.ensureChannels` and `safeNotify` for notifications.
35+
- Calendar writes: prefer `SettingsStore.getSelectedCalendarId()` if set; otherwise pick first visible calendar and always set timezone when inserting.
36+
- Confirmation UI: confirmation notifications include delete-action (`EventActionReceiver`) and deep links to created events — update both when changing behavior.
37+
38+
## Integration & third-party
39+
- `timenlp/internal/*`: regex-based normalizer; used only via `TimeNLPAdapter`.
40+
41+
- ML Kit (full flavor)
42+
- Implementation: `app/src/full/java/top/stevezmt/calsync/MLKitStrategy.kt` (FOSS stub: `app/src/foss/java/.../MLKitStrategy.kt` returns null).
43+
- Dependency: added as `fullImplementation(libs.mlkit.entity.extraction)` (see `gradle/libs.versions.toml` and `app/build.gradle.kts`).
44+
- Behavior: uses Google ML Kit Entity Extraction (`EntityExtraction`) and `DateTimeEntity`; calls `extractor.downloadModelIfNeeded()` (via `Tasks.await`) which may trigger a model download via Google Play Services on first run.
45+
- Testing & agent guidance:
46+
- **Do not** invoke real ML Kit in unit tests; use the FOSS flavor or mock `EntityExtraction` / `EntityExtractor` in instrumentation tests.
47+
- Guard model downloads with try/catch and avoid blocking the UI thread — ML Kit calls should run on background dispatcher as parser strategies are run there.
48+
- If you change ML Kit usage, update settings/UI hints where we check for `com.google.android.gms` presence (see `SettingsActivity.kt`).
49+
50+
- `third_party/llama.cpp` (native module)
51+
- Location: `third_party/llama.cpp/` — this is a full native C/C++ project with its own CI and `.github/copilot-instructions.md` (read that file before changing native code).
52+
- Android integration: `app/src/main/cpp/CMakeLists.txt` adds llama.cpp as a subdirectory and builds a `llama_jni` shared library; NDK version is pinned in `app/build.gradle.kts` (`ndkVersion = "29.0.13599879"`).
53+
- Build notes: changes to `llama.cpp` may require CMake/NDK updates and are often platform-specific; prefer minimal, well-tested changes. Use CPU-only defaults for Android builds; Vulkan/CUDA/Metal require extra toolchains and hardware.
54+
- Testing & agent guidance:
55+
- Run native builds with CMake and the Android NDK; ensure `LLAMA_DIR` exists (CMake will fail otherwise).
56+
- For changes inside `third_party/llama.cpp`, follow its contribution guide and run its CI locally (`ci/run.sh`) or the specific CMake build commands documented in its `.github` docs.
57+
- Avoid modifying vendored code unless necessary — prefer JNI glue (`app/src/main/cpp/llama_jni.cpp`) for app-specific logic.
58+
59+
5760

5861
## Quick examples
59-
- Extract sentences: `DateTimeParser.extractAllSentencesContainingDate(ctx, title + "。" + content)`.
60-
- Parse with fixed base: `DateTimeParser.parseDateTime(ctx, sentence, baseMillis)`; expected to honor prefer-future option.
61-
- Insert event: `CalendarHelper.insertEvent(ctx, title, desc, start, end, location)` then `NotificationUtils.sendEventCreated(...)`.
62+
- Extract: `DateTimeParser.extractAllSentencesContainingDate(ctx, title + "。" + content)`
63+
- Parse with base: `DateTimeParser.parseDateTime(ctx, sentence, baseMillis)`
64+
- Insert & notify: `CalendarHelper.insertEvent(ctx, title, desc, start, end, location)` `NotificationUtils.sendEventCreated(...)`
6265

63-
If any parts of these instructions feel incomplete (e.g., missing timenlp/internal details or ROM-specific quirks you rely on), tell me what to clarify and I’ll amend this file.
66+
---
67+
If anything here is unclear or need more examples (tests, failing-case patterns, or ROM quirks), create a new task to investigate and document it.

app/build.gradle.kts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,22 @@ android {
1818
applicationId = "top.stevezmt.calsync"
1919
minSdk = 23
2020
targetSdk = 36
21-
versionCode = 10
22-
versionName = "0.1.4"
21+
versionCode = 11
22+
versionName = "0.1.5"
2323

2424
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2525

2626
externalNativeBuild {
2727
cmake {
2828
cppFlags += "-std=c++17"
29+
val requestedAbis = project.findProperty("abiFilter")?.toString()?.split(",")
30+
if (requestedAbis != null) {
31+
abiFilters.addAll(requestedAbis)
32+
} else {
33+
// Include riscv64 in the build (will be in universal APK)
34+
// but not necessarily in the splits include list below
35+
abiFilters.addAll(listOf("arm64-v8a", "x86_64", "armeabi-v7a", "x86", "riscv64"))
36+
}
2937
}
3038
}
3139
}
@@ -53,9 +61,10 @@ android {
5361
if (requestedAbis != null) {
5462
include(*requestedAbis.toTypedArray())
5563
} else {
56-
include("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
64+
// Include major architectures and riscv64
65+
include("arm64-v8a", "x86_64", "armeabi-v7a", "x86", "riscv64")
5766
}
58-
isUniversalApk = false
67+
isUniversalApk = true
5968
}
6069
}
6170

@@ -86,6 +95,10 @@ android {
8695

8796
applicationVariants.all {
8897
val baseVersionCode = versionCode
98+
// Fix for F-Droid: Ensure BuildConfig.VERSION_CODE is consistent across ABI splits
99+
// by forcing it to the base version code.
100+
buildConfigField("int", "VERSION_CODE", "${baseVersionCode}")
101+
89102
outputs.all {
90103
val output = this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
91104
val abi = output.getFilter(com.android.build.OutputFile.ABI)
@@ -97,6 +110,7 @@ android {
97110
"arm64-v8a" -> 2
98111
"x86" -> 3
99112
"x86_64" -> 4
113+
"riscv64" -> 5
100114
else -> 0 // universal or others
101115
}
102116

app/src/main/cpp/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ if(ANDROID)
4444
set(GGML_NEON ON CACHE BOOL "ggml: use NEON" FORCE)
4545
message(STATUS "Enabling NEON for arm64-v8a")
4646
endif()
47+
48+
# Disable RVV on RISC-V to avoid toolchain issues with inline assembly
49+
if(ANDROID_ABI STREQUAL "riscv64")
50+
set(GGML_RVV OFF CACHE BOOL "ggml: use RVV" FORCE)
51+
message(STATUS "Disabling RVV for riscv64")
52+
endif()
4753
endif()
4854

4955
# Add llama.cpp as subdirectory

0 commit comments

Comments
 (0)