- NotificationBadgeService: re-posted notifications update the stored badge number instead of being dropped; flag changes (clearable -> ongoing) remove the entry; exclude group summaries (AOSP Launcher3 does too) and USER_ALL (-1) records; log each counts publish. - HomeFragment: register the badge collector once in onViewCreated instead of every onResume (collector leak); rebind the visible home rows immediately when the notification-dots toggle flips. - NotificationDotHelper: plain red dot without the white outline; position tangent to the top-right corner so the circle is never clipped by the icon bitmap.
6.9 KiB
6.9 KiB
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
- The installed app id is
app.easy.launcher— NOT upstream'scom.github.droidworksstudio.launcher. Every fork build must keep this id. - Release APKs are signed with
~/android-keystores/easylauncher-release.jks(aliaseasylauncher, pass ineasylauncher-release.pass). Never commit an unsigned or differently-signed APK todist/. adb install -r dist/...-Signed.apkupdates the phone in place (same signature). A debug build cannot be installed over it (signature mismatch).- 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.
- Never fetch weather without a real location (lat/lon 0,0 must be treated as "no location").
Build
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") andwithoutInternet. - 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)
- Bump
versionCode/versionNameinapp/build.gradle.kts(currently 35 / 0.3.5). - Build both flavors, then sign each:
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
apksigner verify --print-certsshould show the keystore SHA-256.- Commit as
release build vX.Y.Z(includes the dist APKs + .idsig files), annotated tagvX.Y.Z, pushmain+ tag toorigin(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 viauiautomator 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:
- Pull the file, edit locally, push back via stdin:
adb shell "su -c 'cat > <path>'" < localfile - 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. - Run
restorecon -F <file>— files created viasu catget the wrong SELinux context (s0instead ofs0:c18,c257,c512,c768) and the app can't read them (silent empty map). am force-stop app.easy.launcherthen 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 opensACTION_NOTIFICATION_LISTENER_SETTINGSwhen missing. - Badge semantics: per package the service sums
Notification.number(or 1 when unset) over COUNTED notifications — same as AOSP Launcher3. Only swipe-away, non-ongoing notifications are counted (music/FGS excluded). - 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 ascom.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 descriptiveUser-Agentheader (seeMetApiService). Verified working from PC and phone network. AppHelper.fetchMetWeather(context, lat, lon)returns Celsius (rounded) + METsymbol_code; 15-minute cache inmet_weather_prefs.- The icon is a Nerd Font weather glyph (
nf-weather-*, U+E300+ PUA), mapped from the symbol code inMetSymbolMapper. It renders with the bundledR.font.jetbrains_mono_nf_weather— a ~95KB fontTools subset of JetBrainsMonoNerdFont (ASCII + 38 weather glyphs + °). The phone's system font ISJetBrainsMonoNerdFont(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.xmlkeysLATITUDE/LONGITUDE(floats). MainActivity saves the real fix there; for testing you can seed e.g. Haugesund 59.4138 / 5.2680 (remember the.xmlsuffix +restorecon!).
Known behavior quirks
- The accessibility-service dialog ("Please turn on accessibility service to
use double tap to lock") pops on home when
ActionServiceisn't running. Enable the service in system settings to suppress it during testing. Application.setCustomFontpatches theTypefaceDEFAULT/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:
appListTouchAreaforwards vertical drags/ flings throughOnSwipeTouchListenerhooks (onVerticalScroll/onVerticalFling). Signs were verified against AOSP source:scrollBy(0, distanceY)is pass-through; fling takes the NEGATED pointer velocity.