8 Commits
Author SHA1 Message Date
XoséandClaude Opus 5.5 3d3f3e30b5 Add spec.md: goal, limits and rules for working on the project
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:05:33 +01:00
XoséandClaude Opus 5.5 7f3c33c57e build.sh: test that javac runs (macOS's /usr/bin/javac is only a placeholder)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:43:00 +02:00
XoséandClaude Opus 5.5 836a16c911 build.sh: find d8, apktool and apksigner in ~/android-toolchain/calendar-tools, and a JDK, by default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:42:21 +02:00
XoséandClaude Opus 5.5 775aa04cdf Merge branch 'root-cache' into main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:02:43 +02:00
XoséandClaude Opus 5.5 9442afb2bd AssetRoots: keep loaded roots, retry a failed read (from tlskit)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:00:52 +02:00
xocko a625e20a56 Merge pull request 'Calendar Lite 1.6: bundled TLS 1.2, renamed from GingerCal, new icons, English months' (#1) from bundled-tls into main 2026-10-19 17:11:38 +00:00
XoséandClaude Opus 5.5 82acdb79f6 Calendar Lite 1.6: English month and weekday names in English mode
They came from the phone's locale, so a phone set to Spanish showed "Octubre 2026" with an
English interface (and I18nTest failed on any machine not set to English).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:11:24 +02:00
XoséandClaude Opus 5.5 478fc60527 README: direct https (no Pangolin needed), APK size, Bouncy Castle sources, test command
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:40:21 +02:00
7 changed files with 98 additions and 15 deletions

No files matched your search

+2 -2
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.gingercal"
android:versionCode="6"
android:versionName="1.5">
android:versionCode="7"
android:versionName="1.6">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
+10 -5
View File
@@ -1,7 +1,7 @@
# Calendar Lite — Nextcloud CalDAV for Android 2.3 (Galaxy Mini)
A small native calendar client for Gingerbread (API 9/10). Built for a Samsung Galaxy Mini
(GT-S5570, 240×320, Android 2.3.5) talking to Nextcloud, reached through Pangolin.
(GT-S5570, 240×320, Android 2.3.5) talking to Nextcloud at its normal `https://` address.
**Since 1.4 it brings its own TLS 1.2** (Bouncy Castle, the same client as Plexamp Lite, with Mozilla's
root certificates and keep-alive connections), like the iPod version does with BearSSL. Put the
@@ -139,14 +139,19 @@ you should turn `proxy_ssl_verify` on.
Menu key: View (Month/Agenda), New event, Sync, Today, Calendars, Settings. Tap an event for
details, Edit and Delete.
If installation fails, check the file arrived whole (53 KB; some browsers save a truncated or
If installation fails, check the file arrived whole (about 1 MB; some browsers save a truncated or
renamed `.apk.zip`) and that there are a few MB free on the internal storage. On a PC with
`adb`, `adb install CalendarLite.apk` prints the exact reason (`INSTALL_FAILED_...`).
## Building
Needs JDK 11+, Python 3, `d8` and `apksigner.jar` (Android SDK build-tools), and
`apktool.jar` (2.9+, which bundles Google's `aapt` and smali). No Gradle.
Needs JDK 11+, Python 3, `d8` and `apksigner.jar` (Android SDK build-tools),
`apktool.jar` (2.9+, which bundles Google's `aapt` and smali), and the Bouncy Castle sources renamed
to `com.minamp.bc` that Plexamp Lite's `tools/setup_toolchain.sh` prepares (`BC_SRC`, default
`~/android-toolchain/bcsrc`). No Gradle.
With build-tools 34's `d8.jar` and `apksigner.jar` and `apktool_2.9.3.jar` in `~/android-toolchain/calendar-tools`
(where they are on this Mac), just `./build.sh`. Elsewhere:
```sh
D8=d8 APKTOOL=~/apktool.jar APKSIGNER=$SDK/build-tools/34.0.0/lib/apksigner.jar ./build.sh
@@ -165,7 +170,7 @@ over the existing app.
Tests (desktop JVM, need a CalDAV server such as Radicale on :5232 with user `xose`/`secret`):
```sh
javac -d out src/org/gingercal/*.java test/org/gingercal/*.java
javac -d out src/org/gingercal/*.java $(ls test/org/gingercal/*.java | grep -v NetTest)
java -cp out org.gingercal.CoreTest # discovery, sync, recurrence, create/edit/delete
java -cp out org.gingercal.RedirectTest # redirect handling (no server needed)
# the bundled TLS transport (needs the android.jar and Bouncy Castle sources to compile):
+1 -1
View File
@@ -9,7 +9,7 @@ public class GingerApp extends Application {
super.onCreate();
L.gl = !"en".equals(getSharedPreferences("gingercal", 0).getString("lang", "gl"));
org.gingercal.net.CertVerifier.setRootStore(new org.gingercal.net.AssetRoots(this));
org.gingercal.net.Http.USER_AGENT = "CalendarLite/1.5 (Android 2.3)";
org.gingercal.net.Http.USER_AGENT = "CalendarLite/1.6 (Android 2.3)";
tlsTexts();
}
+18 -6
View File
@@ -4,35 +4,47 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import android.content.Context;
/** Mozilla roots shipped as assets/roots/<sha1(subject)>-<n>.der */
/**
* Mozilla roots shipped as assets/roots/<sha1(subject)>-<n>.der
*
* Roots are read from the APK once and kept: an app installed on the SD card loses its APK while the
* card is shared over USB, and a failed read must not leave every server "untrusted" until a restart.
*/
public final class AssetRoots implements CertVerifier.RootStore {
private final Context ctx;
private Set<String> names;
private Set<String> names; // asset file names, once listed
private final Map<String, List<byte[]>> found = new HashMap<String, List<byte[]>>(); // by subject hash; null = none
public AssetRoots(Context c) { ctx = c.getApplicationContext(); }
public synchronized List<byte[]> find(byte[] subjectDer) {
String h = CertVerifier.sha1Hex(subjectDer);
if (found.containsKey(h)) return found.get(h);
try {
if (names == null) {
names = new HashSet<String>();
String[] l = ctx.getAssets().list("roots");
if (l != null) for (int i = 0; i < l.length; i++) names.add(l[i]);
if (l == null || l.length == 0) return null; // APK unreadable right now: try again next time
Set<String> n = new HashSet<String>();
for (int i = 0; i < l.length; i++) n.add(l[i]);
names = n;
}
String h = CertVerifier.sha1Hex(subjectDer);
List<byte[]> out = null;
for (int i = 0; names.contains(h + "-" + i + ".der"); i++) {
if (out == null) out = new ArrayList<byte[]>();
out.add(readAll(ctx.getAssets().open("roots/" + h + "-" + i + ".der")));
}
found.put(h, out);
return out;
} catch (IOException e) {
return null;
return null; // not cached: the next handshake reads again
}
}
+10
View File
@@ -11,7 +11,17 @@
set -eu
cd "$(dirname "$0")"
OUT=build
# defaults: the copies in ~/android-toolchain/calendar-tools (build-tools 34's d8.jar and apksigner.jar, apktool 2.9.3)
CT=${CALENDAR_TOOLS:-$HOME/android-toolchain/calendar-tools}
if [ -z "${D8:-}" ] && [ -f "$CT/d8.jar" ]; then D8="java -cp $CT/d8.jar com.android.tools.r8.D8"; fi
D8=${D8:-d8}
if [ -z "${APKTOOL:-}" ] && [ -f "$CT/apktool_2.9.3.jar" ]; then APKTOOL=$CT/apktool_2.9.3.jar; fi
if [ -z "${APKSIGNER:-}" ] && [ -f "$CT/apksigner.jar" ]; then APKSIGNER=$CT/apksigner.jar; fi
if ! javac -version >/dev/null 2>&1; then # macOS has a /usr/bin/javac that only says Java is missing
for j in "${JAVA_HOME:-}" /opt/homebrew/opt/openjdk@17 /opt/homebrew/opt/openjdk; do
if [ -n "$j" ] && [ -x "$j/bin/javac" ]; then JAVA_HOME=$j; PATH=$j/bin:$PATH; export JAVA_HOME PATH; break; fi
done
fi
APKTOOL=${APKTOOL:?set APKTOOL=/path/to/apktool.jar}
APKSIGNER=${APKSIGNER:?set APKSIGNER=/path/to/apksigner.jar}
KS=${KEYSTORE:-gingercal.keystore}
+56
View File
@@ -0,0 +1,56 @@
# Calendar Lite (Android) — spec
The short contract for working on this app. `README.md` is the user guide (and keeps the old TLS-bridge
setup); this file says what the app is for, what limits it lives within, and what must not break.
## Goal
A native Nextcloud (CalDAV) calendar for the Samsung Galaxy Mini that works at home and away, offline,
with reminders.
## Target and hard limits
- Samsung Galaxy Mini GT-S5570, Android 2.3.5 (API 9/10, `minSdkVersion` 8), 240×320 ldpi.
- Native Java, no libraries beyond the bundled TLS stack. No calendar-provider integration (2.3 has no
public API for it).
- Galician by default (weeks start Monday, month/day names built in), English in Settings.
## Current scope (1.6)
- Month view (6-week grid, colour marks, swipe, long-press to create) and Agenda view; choice remembered.
- SQLite cache of the last 35 days and the next 60 ("Days ahead"); months outside it fetched on demand.
- Create, edit and delete events (title, all-day, start/end, location, notes, alarm). Repeating events are
shown (server expansion, client fallback) and can be deleted, but are edited only in Nextcloud.
- Pick visible calendars and the default one for new events.
- Background sync every 1–24 h; notifications N minutes before timed events.
- Bundled TLS 1.2 to the normal `https://` address; pinned certificates for self-signed servers or the old
TLS 1.0 bridge (still supported).
## Out of scope
- Editing repeating events, tasks/VTODO, contacts, feeding the stock Samsung calendar.
## Must not break
- Package `org.gingercal` and `gingercal.keystore` (password `android`): updates install over GingerCal.
- Signing with `apksigner` v1 only; never `jarsigner` (that was the first build's bug).
- `src/` stays platform-neutral (CalDAV core runs on a desktop JVM for tests); Android code lives in
`android/`.
- `android/org/gingercal/net/` is a copy of `tlskit`, copied by hand (its `install.sh` doesn't handle this
path): change it in `../tlskit` first.
- `bridge/` stays working for anyone still on a bridge address.
## Code map
`src/` discovery, PROPFIND/REPORT, iCalendar, sync, pinning · `android/` activities, `SyncService`, alarms,
`Db` (SQLite), `NetHttp` · `stubs/` compile-only API 10 signatures · `bridge/` nginx legacy-TLS endpoint ·
`tools/` dex/API checkers, zipalign, d8 workaround.
## Build, test, release
- `./build.sh` (finds d8, apksigner and apktool in `~/android-toolchain/calendar-tools`, and a JDK). Its own
route: javac → d8 → baksmali/smali → aapt v1 → apksigner v1 → zipalign → `apksigner verify` for API 8–10.
- Tests on the desktop JVM: `CoreTest` (needs Radicale on :5232, user `xose`/`secret`), `RedirectTest`,
`I18nTest`, `NetTest`. `tools/check_i18n.py` and `tools/check_api.py` run in the build.
- Release: raise `versionCode`/`versionName`, build, add `changes` in `../tenda-lite/repo/apps.json`, commit
and push, `../tenda-lite/repo/publish.sh`.
+1 -1
View File
@@ -42,7 +42,7 @@ public final class L {
static final String[] GL_DAYS_SHORT = {"", "dom", "lun", "mar", "mér", "xov", "ven", "sáb"};
public static DateFormatSymbols symbols() {
if (!gl) return new DateFormatSymbols(Locale.getDefault());
if (!gl) return new DateFormatSymbols(Locale.US); // English names, whatever the phone's language
DateFormatSymbols s = new DateFormatSymbols(Locale.US);
s.setMonths(GL_MONTHS);
s.setShortMonths(GL_MONTHS_SHORT);