diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7d811a1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,131 @@ +# Working with this EasyLauncher fork + +Quick reference for agents working on this fork (Gitea: `jonas/EasyLauncher`, +installed on the Unihertz Titan 2 Elite as `app.easy.launcher`). + +**PROJECT.md in ~/titan2-elite is the source of truth for the phone itself.** + +## Golden rules + +1. The installed app id is **`app.easy.launcher`** — NOT upstream's + `com.github.droidworksstudio.launcher`. Every fork build must keep this id. +2. Release APKs are signed with `~/android-keystores/easylauncher-release.jks` + (alias `easylauncher`, pass in `easylauncher-release.pass`). Never commit an + unsigned or differently-signed APK to `dist/`. +3. `adb install -r dist/...-Signed.apk` updates the phone in place (same + signature). A debug build cannot be installed over it (signature mismatch). +4. After a phone reboot the phone is CE-locked until the user enters the PIN; + app prefs/location are not visible to the app until then. Ask the user to + unlock instead of debugging it. +5. Never fetch weather without a real location (lat/lon 0,0 must be treated as + "no location"). + +## Build + +```bash +export JAVA_HOME=~/jdk21 # Gradle 8.13, compileSdk/targetSdk 36, minSdk 24 +./gradlew :app:compileWithInternetReleaseKotlin :app:compileWithoutInternetReleaseKotlin --offline +./gradlew :app:assembleWithInternetRelease :app:assembleWithoutInternetRelease --offline +``` + +- Two flavors: `withInternet` (INTERNET + location permissions, "Easy Launcher") + and `withoutInternet`. +- Outputs: `app/build/outputs/apk/{withInternet,withoutInternet}/release/`. +- `weather.properties` (OpenWeatherMap key) is optional and absent here — the + OWM weather widget is dead without it. The home-screen current-weather + element uses the MET/Yr API instead (no key needed, see below). + +## Release flow (see git history for the v0.3.x pattern) + +1. Bump `versionCode`/`versionName` in `app/build.gradle.kts` + (currently 35 / 0.3.5). +2. Build both flavors, then sign each: + +```bash +PASS=$(cat ~/android-keystores/easylauncher-release.pass) +~/android-sdk/build-tools/36.0.0/apksigner sign --v4-signing-enabled true \ + --ks ~/android-keystores/easylauncher-release.jks --ks-key-alias easylauncher \ + --ks-pass "pass:$PASS" --key-pass "pass:$PASS" \ + --out dist/EasyLauncher-Internet-v0.3.5-Signed.apk \ + app/build/outputs/apk/withInternet/release/app.easy.launcher_v0.3.5-Release.apk +# repeat for withoutInternet -> dist/EasyLauncher-v0.3.5-Signed.apk +``` + +3. `apksigner verify --print-certs` should show the keystore SHA-256. +4. Commit as `release build vX.Y.Z` (includes the dist APKs + .idsig files), + annotated tag `vX.Y.Z`, push `main` + tag to `origin` (Gitea), create a + Gitea release with both APKs as assets. + +## On-device testing (Titan 2 Elite via adb) + +- Wake: `input keyevent KEYCODE_WAKEUP`; go home: `input keyevent KEYCODE_HOME`. +- Open the app drawer: `input keyevent KEYCODE_SPACE` (the physical-keyboard + "key press -> app list" trigger; requires MainActivity focused). +- Scroll the drawer from the right edge: `input swipe 1000 850 1000 250 250`. +- Screenshot: `adb exec-out screencap -p > s.png`; view text via + `uiautomator dump`. +- Inspect the launcher's own state with `su -c '...'` (root via Magisk). + +### Editing app prefs from the host (fragile — read carefully) + +Prefs live in `/data/data/app.easy.launcher/shared_prefs/`. To change them: + +1. Pull the file, edit locally, push back via stdin: + `adb shell "su -c 'cat > '" < localfile` +2. **The file name must end in `.xml`** (`EasyLauncher.pref.xml`, + `EasyWeather.pref.xml`) — `getSharedPreferences("name")` appends `.xml`. + A missing suffix silently reads as an EMPTY map. +3. **Run `restorecon -F `** — files created via `su cat` get the wrong + SELinux context (`s0` instead of `s0:c18,c257,c512,c768`) and the app can't + read them (silent empty map). +4. `am force-stop app.easy.launcher` then press HOME so the process restarts + and re-reads the file. + +### Notification dots + +- Service: `.service.NotificationBadgeService` (NotificationListenerService). + Users must grant notification access; the "Notification Dots" settings toggle + opens `ACTION_NOTIFICATION_LISTENER_SETTINGS` when missing. +- Grant from adb: + `cmd notification allow_listener app.easy.launcher/com.github.droidworksstudio.launcher.service.NotificationBadgeService` +- Test notification: `cmd notification post -t 'Title' tag 'body'` — posts as + `com.android.shell`, which has no launcher icon, so no dot is visible; verify + visually with a real app notification instead. +- Counts live in `NotificationBadgeService.notificationCounts` + (StateFlow, key `"userId/packageName"`); fragments rebind visible rows. + +### Home current-weather element (MET/Yr) + +- Source: `https://api.met.no/weatherapi/locationforecast/2.0/compact` — **no API + key**, but REQUIRES a descriptive `User-Agent` header (see `MetApiService`). + Verified working from PC and phone network. +- `AppHelper.fetchMetWeather(context, lat, lon)` returns Celsius (rounded) + + MET `symbol_code`; 15-minute cache in `met_weather_prefs`. +- The icon is a **Nerd Font weather glyph** (`nf-weather-*`, U+E300+ PUA), + mapped from the symbol code in `MetSymbolMapper`. It renders with the bundled + `R.font.jetbrains_mono_nf_weather` — a ~95KB fontTools subset of + JetBrainsMonoNerdFont (ASCII + 38 weather glyphs + °). The phone's system + font IS `JetBrainsMonoNerdFont` (fonts.xml default), but the bundled subset + guarantees rendering even if the launcher-font setting changes. Regenerate + the subset with fontTools if more glyphs are ever needed (source: + `/system/fonts/JetBrainsMonoNerdFont-Regular.ttf`). +- Display mirrors the daily word (uses the daily-word color/size/alignment + prefs); text = `"$glyph $temp°"` (two spaces). +- Needs a real saved location: `EasyWeather.pref.xml` keys `LATITUDE`/ + `LONGITUDE` (floats). MainActivity saves the real fix there; for testing you + can seed e.g. Haugesund 59.4138 / 5.2680 (remember the `.xml` suffix + + `restorecon`!). + +## Known behavior quirks + +- The accessibility-service dialog ("Please turn on accessibility service to + use double tap to lock") pops on home when `ActionService` isn't running. + Enable the service in system settings to suppress it during testing. +- `Application.setCustomFont` patches the `Typeface` DEFAULT/MONO/SERIF/SANS + static fields when a non-System launcher font is selected. +- With "Disable Animations" on, the drawer RecyclerView item animator is null + and navigation transitions are skipped. +- Drawer whole-screen scrolling: `appListTouchArea` forwards vertical drags/ + flings through `OnSwipeTouchListener` hooks (`onVerticalScroll`/`onVerticalFling`). + Signs were verified against AOSP source: `scrollBy(0, distanceY)` is + pass-through; fling takes the NEGATED pointer velocity.