docs: add CLAUDE.md with build, signing, and on-device testing notes
This commit is contained in:
131
CLAUDE.md
Normal file
131
CLAUDE.md
Normal file
@@ -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 > <path>'" < 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 <file>`** — 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.
|
||||
Reference in New Issue
Block a user