Calendar Lite 1.5: GingerCal is now Calendar Lite, new icon and reminder icon
Renames the app (label, texts, User-Agent, PRODID, APK) and uses the new launcher icon pack (ldpi to xhdpi, master in art/) instead of drawing one at build time. Reminders show a white calendar glyph in the status bar (stat_notify, 18-48 px) instead of the system's colour alarm clock, which didn't fit the Gingerbread bar. The package name, database and signing key stay, so it installs over GingerCal and keeps its data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -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="5"
|
||||
android:versionName="1.4">
|
||||
android:versionCode="6"
|
||||
android:versionName="1.5">
|
||||
|
||||
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# GingerCal — Nextcloud CalDAV for Android 2.3 (Galaxy Mini)
|
||||
# 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.
|
||||
|
||||
**Since 1.4 it brings its own TLS 1.2** (Bouncy Castle, the same client as Minamp, with Mozilla's
|
||||
**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
|
||||
normal `https://` Nextcloud address in Settings, at home or away: no bridge is needed. The bridge
|
||||
sections below are kept for reference; a bridge address (pinned certificate) still works.
|
||||
@@ -131,9 +131,9 @@ you should turn `proxy_ssl_verify` on.
|
||||
## Install on the phone
|
||||
|
||||
1. Settings, Applications, tick **Unknown sources**.
|
||||
2. Copy `GingerCal.apk` to the SD card (USB) and open it with the file manager, or download it
|
||||
2. Copy `CalendarLite.apk` to the SD card (USB) and open it with the file manager, or download it
|
||||
over plain `http://` (the 2.3 browser can't open most HTTPS sites either).
|
||||
3. Open GingerCal, Menu, Settings. In Nextcloud create an app password: Settings, Security,
|
||||
3. Open Calendar Lite, Menu, Settings. In Nextcloud create an app password: Settings, Security,
|
||||
Devices & sessions. Fill in server, user, app password, then **Save and test connection**.
|
||||
|
||||
Menu key: View (Month/Agenda), New event, Sync, Today, Calendars, Settings. Tap an event for
|
||||
@@ -141,11 +141,11 @@ details, Edit and Delete.
|
||||
|
||||
If installation fails, check the file arrived whole (53 KB; 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 GingerCal.apk` prints the exact reason (`INSTALL_FAILED_...`).
|
||||
`adb`, `adb install CalendarLite.apk` prints the exact reason (`INSTALL_FAILED_...`).
|
||||
|
||||
## Building
|
||||
|
||||
Needs JDK 11+, Python 3 with Pillow, `d8` and `apksigner.jar` (Android SDK build-tools), and
|
||||
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.
|
||||
|
||||
```sh
|
||||
@@ -181,12 +181,12 @@ declarations.
|
||||
|
||||
- `src/` CalDAV core, platform-neutral: discovery, PROPFIND/REPORT, iCalendar, sync, pinning
|
||||
- `android/` activities, sync service, alarms, SQLite cache, `NetHttp` transport
|
||||
- `android/org/gingercal/net/` the bundled TLS 1.2 HTTP client from Minamp; `assets/roots/` Mozilla roots
|
||||
- `android/org/gingercal/net/` the bundled TLS 1.2 HTTP client from Plexamp Lite; `assets/roots/` Mozilla roots
|
||||
- `stubs/` compile-only Android API 10 signatures (never packaged)
|
||||
- `bridge/` legacy-TLS endpoint: standalone nginx (config, compose file), `npm/http.conf` for
|
||||
Nginx Proxy Manager, certificate script
|
||||
- `res/` app name; the icon is drawn at build time (mdpi + ldpi for the Galaxy Mini)
|
||||
- `tools/` icon, dex disassembler helper, zipalign, dex/API checkers, d8 workaround
|
||||
- `res/` app name, launcher icon (ldpi to xhdpi; master in `art/`) and `stat_notify`, the white status-bar icon for reminders
|
||||
- `tools/` dex disassembler helper, zipalign, dex/API checkers, d8 workaround
|
||||
|
||||
## Security notes
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public class AlarmReceiver extends BroadcastReceiver {
|
||||
static synchronized void acquire(Context c) {
|
||||
if (lock == null) {
|
||||
PowerManager pm = (PowerManager) c.getApplicationContext().getSystemService("power");
|
||||
lock = pm.newWakeLock(1 /* PARTIAL_WAKE_LOCK */, "GingerCal sync");
|
||||
lock = pm.newWakeLock(1 /* PARTIAL_WAKE_LOCK */, "Calendar Lite sync");
|
||||
lock.setReferenceCounted(false);
|
||||
}
|
||||
lock.acquire(5 * 60 * 1000L);
|
||||
|
||||
@@ -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 = "GingerCal/1.4 (Android 2.3)";
|
||||
org.gingercal.net.Http.USER_AGENT = "CalendarLite/1.5 (Android 2.3)";
|
||||
tlsTexts();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.concurrent.ExecutorService;
|
||||
import org.gingercal.net.CertVerifier;
|
||||
|
||||
/**
|
||||
* Android 2.3 transport through the bundled TLS 1.2 client (org.gingercal.net, from Minamp):
|
||||
* Android 2.3 transport through the bundled TLS 1.2 client (org.gingercal.net, from Plexamp Lite):
|
||||
* the phone's own TLS 1.0 can't reach Nextcloud, so this talks to the normal https:// address
|
||||
* directly, any method (PROPFIND/REPORT included), with keep-alive. A pinned certificate (a
|
||||
* self-signed server or an old bridge) is still honoured.
|
||||
|
||||
@@ -43,8 +43,9 @@ public final class Reminders {
|
||||
List<Ev> due = Db.get(c).startingBetween(Math.max(notified, now - 30 * ICal.MIN), now + m * ICal.MIN + 30000L);
|
||||
NotificationManager nm = (NotificationManager) c.getSystemService("notification");
|
||||
long newest = notified;
|
||||
int icon = App.sysDrawable(c, "ic_popup_reminder");
|
||||
if (icon == 0) icon = App.sysDrawable(c, "ic_dialog_info");
|
||||
// our own white status-bar glyph: the system's colour alarm-clock icon looks out of place up there
|
||||
int icon = c.getResources().getIdentifier("stat_notify", "drawable", c.getPackageName());
|
||||
if (icon == 0) icon = App.sysDrawable(c, "ic_popup_reminder");
|
||||
for (Ev e : due) {
|
||||
Intent open = new Intent(c, MainActivity.class);
|
||||
open.setFlags(0x10000000 /* FLAG_ACTIVITY_NEW_TASK */);
|
||||
|
||||
@@ -27,7 +27,7 @@ public class SettingsActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle b) {
|
||||
super.onCreate(b);
|
||||
setTitle(L.t("GingerCal settings"));
|
||||
setTitle(L.t("Calendar Lite settings"));
|
||||
LinearLayout f = App.vbox(this);
|
||||
int p = App.dp(this, 8);
|
||||
f.setPadding(p, p, p, p);
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.minamp.bc.crypto.util.PublicKeyFactory;
|
||||
* Minimal Web PKI path validation for the bundled TLS stack:
|
||||
* hostname match, validity window, signature chain up to a bundled Mozilla root.
|
||||
* Optionally one pinned leaf certificate (SHA-256) is trusted as-is, for self-signed servers.
|
||||
* Shared with Minamp (tools/tlskit in the iOS ports' history); keep the copies in sync.
|
||||
* Shared with Plexamp Lite (tools/tlskit in the iOS ports' history); keep the copies in sync.
|
||||
*/
|
||||
public final class CertVerifier {
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ import com.minamp.bc.tls.crypto.impl.bc.BcTlsCrypto;
|
||||
/**
|
||||
* Small HTTP/1.1 client with its own TLS 1.2 (Bouncy Castle), keep-alive pooling and
|
||||
* session resumption. Android 2.x's built-in SSL only speaks TLS 1.0 and lacks today's roots,
|
||||
* so every request in the app goes through here. From Minamp; keep the copies in sync.
|
||||
* so every request in the app goes through here. From Plexamp Lite; keep the copies in sync.
|
||||
*/
|
||||
public final class Http {
|
||||
|
||||
public static String USER_AGENT = "Minamp/1.0";
|
||||
public static String USER_AGENT = "PlexampLite/1.0";
|
||||
|
||||
/** Optional HTTP CONNECT proxy, only used when running the networking code on a desktop JVM. */
|
||||
public static String proxyHost;
|
||||
|
||||
|
After Width: | Height: | Size: 973 KiB |
@@ -1,5 +1,5 @@
|
||||
TASK FOR CLAUDE CODE (on the Pangolin server, 141.227.129.182)
|
||||
Add a raw TCP resource on port 8443 so the GingerCal app (Android 2.3) can reach Nextcloud CalDAV
|
||||
Add a raw TCP resource on port 8443 so the Calendar Lite app (Android 2.3) can reach Nextcloud CalDAV
|
||||
==================================================================================================
|
||||
|
||||
CONTEXT
|
||||
@@ -72,7 +72,7 @@ STEPS
|
||||
|
||||
7. Pangolin dashboard (the user does this, or guide them through it):
|
||||
- Org of the home server, Resources, Add Resource, type "Raw TCP/UDP resource".
|
||||
- Name: GingerCal CalDAV. Protocol: TCP. Public port: 8443.
|
||||
- Name: Calendar Lite CalDAV. Protocol: TCP. Public port: 8443.
|
||||
- Site: the site whose newt runs on the home server 172.30.1.123 (most likely the "casa" site).
|
||||
- Target: IP 172.30.1.123, port 8443. Leave "Enable Proxy Protocol" OFF (the bridge doesn't
|
||||
expect it; enabling only one side breaks every connection).
|
||||
@@ -104,7 +104,7 @@ STEPS
|
||||
|
||||
PHONE SETUP (after step 8 passes)
|
||||
---------------------------------
|
||||
GingerCal, Menu, Settings:
|
||||
Calendar Lite, Menu, Settings:
|
||||
Server: https://141.227.129.182:8443 (any *.oxose.org name works too, e.g.
|
||||
https://cal-old.oxose.org:8443: raw TCP ignores the hostname and the app pins the
|
||||
certificate, not the name)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Builds GingerCal.apk for Android 2.3 (API 10) without Gradle.
|
||||
# Builds CalendarLite.apk for Android 2.3 (API 10) without Gradle.
|
||||
#
|
||||
# Needs: JDK 11+ (javac, keytool), python3 with Pillow (icon), and three tools:
|
||||
# Needs: JDK 11+ (javac, keytool), python3, and three tools:
|
||||
# D8 command that runs d8 (Android SDK build-tools; default "d8" on PATH)
|
||||
# APKTOOL path to apktool.jar (https://apktool.org; bundles aapt and smali)
|
||||
# APKSIGNER path to apksigner.jar (Android SDK build-tools/<ver>/lib/apksigner.jar)
|
||||
@@ -20,7 +20,7 @@ VERSION_CODE=$(sed -n 's/.*android:versionCode="\([0-9]*\)".*/\1/p' AndroidManif
|
||||
VERSION_NAME=$(sed -n 's/.*android:versionName="\([^"]*\)".*/\1/p' AndroidManifest.xml)
|
||||
|
||||
rm -rf "$OUT"
|
||||
mkdir -p "$OUT/stubs" "$OUT/classes" "$OUT/dex" "$OUT/apk/res/drawable" "$OUT/apk/res/drawable-ldpi"
|
||||
mkdir -p "$OUT/stubs" "$OUT/classes" "$OUT/dex" "$OUT/apk/res"
|
||||
|
||||
# 0. every interface string must have its Galician translation
|
||||
python3 tools/check_i18n.py .
|
||||
@@ -46,13 +46,11 @@ java -cp "$APKTOOL:$OUT/tools" Disassemble "$OUT/dex/classes.dex" "$OUT/apk/smal
|
||||
|
||||
# 3. resources + manifest, compiled by real aapt (v1, the tool of the Gingerbread era)
|
||||
cp AndroidManifest.xml "$OUT/apk/"
|
||||
cp -r res/values "$OUT/apk/res/"
|
||||
cp -r res/* "$OUT/apk/res/" # launcher icon (ldpi..xhdpi) and the status-bar reminder icon
|
||||
cp -r assets "$OUT/apk/" # Mozilla root certificates for the bundled TLS
|
||||
python3 tools/mkicon.py "$OUT/apk/res/drawable/icon.png" 48
|
||||
python3 tools/mkicon.py "$OUT/apk/res/drawable-ldpi/icon.png" 36
|
||||
cat > "$OUT/apk/apktool.yml" <<EOF
|
||||
version: 2.9.3
|
||||
apkFileName: GingerCal.apk
|
||||
apkFileName: CalendarLite.apk
|
||||
isFrameworkApk: false
|
||||
usesFramework:
|
||||
ids:
|
||||
@@ -80,14 +78,14 @@ java -jar "$APKTOOL" -q b --use-aapt1 -api 8 -f -o "$OUT/unsigned.apk" "$OUT/apk
|
||||
# attributes; apksigner picks exactly that for --min-sdk-version 8. (jarsigner does not.)
|
||||
if [ ! -f "$KS" ]; then
|
||||
keytool -genkeypair -keystore "$KS" -storetype PKCS12 -storepass android -keypass android \
|
||||
-alias gingercal -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 10000 -dname "CN=GingerCal" 2>/dev/null
|
||||
-alias gingercal -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 10000 -dname "CN=Calendar Lite" 2>/dev/null
|
||||
fi
|
||||
java -jar "$APKSIGNER" sign --ks "$KS" --ks-key-alias gingercal --ks-pass pass:android --key-pass pass:android \
|
||||
--min-sdk-version 8 --v1-signing-enabled true --v2-signing-enabled false --v3-signing-enabled false \
|
||||
--v4-signing-enabled false --out "$OUT/signed.apk" "$OUT/unsigned.apk"
|
||||
python3 tools/zipalign.py "$OUT/signed.apk" GingerCal.apk
|
||||
python3 tools/zipalign.py "$OUT/signed.apk" CalendarLite.apk
|
||||
|
||||
# 5. refuse to ship anything Android 2.3 would reject
|
||||
java -jar "$APKSIGNER" verify --min-sdk-version 8 --max-sdk-version 10 GingerCal.apk
|
||||
python3 tools/check_dex.py GingerCal.apk
|
||||
ls -l GingerCal.apk
|
||||
java -jar "$APKSIGNER" verify --min-sdk-version 8 --max-sdk-version 10 CalendarLite.apk
|
||||
python3 tools/check_dex.py CalendarLite.apk
|
||||
ls -l CalendarLite.apk
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 499 B |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 666 B |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">GingerCal</string>
|
||||
<string name="app_name">Calendar Lite</string>
|
||||
</resources>
|
||||
@@ -431,7 +431,7 @@ public class ICal {
|
||||
List<String> l = new ArrayList<String>();
|
||||
l.add("BEGIN:VCALENDAR");
|
||||
l.add("VERSION:2.0");
|
||||
l.add("PRODID:-//GingerCal//Android 2.3//EN");
|
||||
l.add("PRODID:-//CalendarLite//Android 2.3//EN");
|
||||
l.add("BEGIN:VEVENT");
|
||||
l.add("UID:" + uid);
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
@@ -149,7 +149,7 @@ public final class L {
|
||||
put("Default calendar", "Calendario predeterminado");
|
||||
|
||||
// settings
|
||||
put("GingerCal settings", "Configuración de GingerCal");
|
||||
put("Calendar Lite settings", "Configuración de Calendar Lite");
|
||||
put("Nextcloud server: your normal https:// address. This app brings its own modern TLS, so no bridge is needed, at home or away. Use an app password, not your real one.",
|
||||
"Servidor Nextcloud: o teu enderezo https:// de sempre. Esta aplicación trae o seu propio TLS moderno, así que non fai falta ningunha ponte, nin na casa nin fóra. Usa un contrasinal de aplicación, non o teu contrasinal real.");
|
||||
put("Server address", "Enderezo do servidor");
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Draws the launcher icon: a little calendar page in ginger tones.
|
||||
usage: mkicon.py out.png [size] (48 = mdpi, 36 = ldpi like the Galaxy Mini)"""
|
||||
import sys
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
S = 4 # supersample then shrink for smooth edges
|
||||
W = 48 * S
|
||||
img = Image.new("RGBA", (W, W), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(img)
|
||||
# shadow
|
||||
d.rounded_rectangle((5 * S, 7 * S, 45 * S, 46 * S), radius=6 * S, fill=(0, 0, 0, 90))
|
||||
# page
|
||||
d.rounded_rectangle((3 * S, 5 * S, 43 * S, 44 * S), radius=6 * S, fill=(250, 246, 238, 255))
|
||||
# ginger header band
|
||||
d.rounded_rectangle((3 * S, 5 * S, 43 * S, 17 * S), radius=6 * S, fill=(214, 110, 42, 255))
|
||||
d.rectangle((3 * S, 11 * S, 43 * S, 17 * S), fill=(214, 110, 42, 255))
|
||||
# binder rings
|
||||
for x in (13, 33):
|
||||
d.rounded_rectangle(((x - 2) * S, 2 * S, (x + 2) * S, 10 * S), radius=2 * S, fill=(90, 60, 40, 255))
|
||||
# day grid, one day highlighted
|
||||
for row in range(3):
|
||||
for col in range(4):
|
||||
x0 = (8 + col * 8.5) * S
|
||||
y0 = (21 + row * 7.5) * S
|
||||
box = (x0, y0, x0 + 5.5 * S, y0 + 5 * S)
|
||||
if (row, col) == (1, 2):
|
||||
d.rounded_rectangle(box, radius=1.5 * S, fill=(0, 130, 201, 255)) # Nextcloud blue
|
||||
else:
|
||||
d.rounded_rectangle(box, radius=1.5 * S, fill=(200, 188, 172, 255))
|
||||
size = int(sys.argv[2]) if len(sys.argv) > 2 else 48
|
||||
img = img.resize((size, size), Image.LANCZOS)
|
||||
img.save(sys.argv[1] if len(sys.argv) > 1 else "icon.png", optimize=True)
|
||||