- Replace internal MetApiService/MetForecastResponse + Retrofit MET fetch in AppHelper with dk.haugesenspil:met-weather (Gitea maven). - fetchMetWeather now delegates to MetWeatherClient; 15-min cache kept. - Drop MetApiService.kt and MetForecastResponse.kt. release build v0.3.7
11 KiB
Task: Add home-screen app-widget hosting to EasyLauncher
You are extending the EasyLauncher fork at ~/sources/EasyLauncher (branch main).
Goal: let the user place real Android app widgets (in particular K-9 Mail's
"K-9 Unread" widget) on the launcher home screen, rendered live and persisted
across reboots.
Read CLAUDE.md in the repo root first — it is the fork's operating manual
(build, signing, on-device testing, quirks). Follow it.
Why (background)
The launcher shows a red dot on app icons for active notifications
(NotificationBadgeService). K-9 Mail cancels its new-mail notification when
the app is opened, so the dot disappears even when unread mail remains. There
is no Android API to read another app's unread count. The clean, official
workaround is K-9's own "K-9 Unread" widget, which shows a persistent
unread count that only goes to zero when the mail is read. EasyLauncher
currently cannot host third-party app widgets (its "Widgets" page,
WidgetFragment.kt, is only self-drawn battery/clock widgets). This task adds
real app-widget hosting.
Scope guard: do NOT implement root-based database reading, IMAP polling, or changes to K-9. Widget hosting only.
Environment
- Repo:
~/sources/EasyLauncher, branchmain, origin = Giteajonas/EasyLauncher. Noupstreamremote. Commit locally with conventional messages (see git history:feat: ...,fix: ...,docs: ...). Do not push, tag, or create a Gitea release unless the user asks. - Stack: Kotlin, minSdk 24, compileSdk/targetSdk 36, Java 17, Hilt (kapt),
Room, ViewBinding (+ DataBinding enabled), two product flavors
(
withInternet/withoutInternet), R8 enabled in release. - Build (offline, JDK 21):
Outputs:
export JAVA_HOME=~/jdk21 ./gradlew :app:compileWithInternetReleaseKotlin :app:compileWithoutInternetReleaseKotlin --offline ./gradlew :app:assembleWithInternetRelease :app:assembleWithoutInternetRelease --offlineapp/build/outputs/apk/{withInternet,withoutInternet}/release/. - Signing (for installing on the phone):
Do NOT bump versionCode/versionName (current 36 / 0.3.6) and do NOT write into
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 /tmp/EasyLauncher-widgethost.apk \ app/build/outputs/apk/withInternet/release/app.easy.launcher_v0.3.6-Release.apkdist/— this is a feature build, not a release. Installing with the same versionCode works:adb install -r /tmp/EasyLauncher-widgethost.apk. - Phone: Unihertz Titan 2 Elite, adb serial
TITAN20000043119, rooted (Magisk).app.easy.launcher(signed release) is installed and is the default home app. K-9 Mail (com.fsck.k9) is installed with 3 accounts. Device facts: after a reboot the phone is CE-locked until the user enters the PIN — if something "doesn't work" right after a reboot, ask the user to unlock before debugging. - Screenshots:
adb exec-out screencap -p > /tmp/s.png; UI dump:adb shell uiautomator dump /sdcard/ui.xml && adb pull /sdcard/ui.xml. Logs:adb logcat | grep -i widgethost.
What to build
1. Widget host manager (the core plumbing)
Create a Hilt @Singleton manager (e.g.
app/src/main/java/com/github/droidworksstudio/launcher/service/WidgetHostManager.kt,
or helper/ if that fits better — follow existing package conventions) that
wraps:
AppWidgetHost(context, hostId)with a unique constant hostId.AppWidgetManager.getInstance(context).
Responsibilities:
startListening()/stopListening()— delegate toAppWidgetHost.- Restore placed widgets — re-create
AppWidgetHostViews from persisted widget ids after process death / reboot:- For each stored
(appWidgetId, component):appWidgetManager.getAppWidgetInfo(id); if null (unbound/removed), drop the entry (host.deleteAppWidget(id)is not needed for missing info; just clear the record).- Else
val view = host.createView(context, id, info)and add to the home widget container.createViewbinds the widget; the view updates automatically while the host is listening.
- For each stored
requestAddWidget(component, callback)— the add flow:val id = host.allocateAppWidgetId()if (!appWidgetManager.bindAppWidgetIdIfAllowed(id, component))→ the app is not the active launcher; notify the user ("Set Easy Launcher as the default home app") and abort.- If the provider has a configure activity
(
providerInfo.configure != null), launch it withappWidgetManager.startAppWidgetConfigureActivityForResult(activity, id, REQUEST_ADD_WIDGET, Bundle())(4-arg version, API 17+, fine for minSdk 24; verify the exact signature against the local SDK — compileSdk 36 sources are available). Resume ononActivityResult:RESULT_OK→ continue; otherwisehost.deleteAppWidget(id)and abort. - Create and return the bound
AppWidgetHostView(host.createView(context, id, info)).
removeWidget(view, id)— remove view from container,host.deleteAppWidget(id), clear the persisted record.- Persistence — store placed widgets in SharedPreferences (follow
PreferenceHelperpatterns; JSON string ofid|componentpairs, key e.g.HOSTED_WIDGETS). Room is acceptable but prefs match the fork's style. - Keep it dependency-light and testable. Log with tag
WidgetHost.
2. Home screen widget area
fragment_home.xml has a vertical mainView LinearLayout:
blockView (battery/clock/date/weather) on top, appListTouchArea
(RecyclerView grid) below.
- Insert a full-width widget container between
blockViewandappListTouchArea, e.g. aFrameLayout(or verticalLinearLayoutfor stacking)android:id="@+id/widgetHostArea",visibility="gone"when no widgets are placed. Keep margins consistent with the rest of the screen (20dp horizontal, mirroringblockView). - Wire in
HomeFragment.kt:onStart:widgetHostManager.startListening(); restore placed widgets into the container.onStop:stopListening().- Handle the add result (
onActivityResultREQUEST_ADD_WIDGET) by inserting the returnedAppWidgetHostView, then persist and show the container. - Removing: long-press a hosted widget → confirm dialog → remove.
- "Add widget" affordance when the feature is enabled: a small "+" in the
widget area when empty (and/or a settings entry — see below). Do not
break existing swipe/gesture handlers (
touchArea,appListTouchAreaforwards vertical drags — the new area sits outsideappListTouchAreaso no conflict, but verify touches on the widget still work). - If the widget area is
gonewhen empty, make sure the home grid layout does not jump when the first widget is added.
3. Widget picker
A bottom sheet or AlertDialog listing installed widget providers:
appWidgetManager.getInstalledProviders()(deprecated on API 33+ — use the profile variantgetInstalledProvidersForProfile(UserHandle.CURRENT)on API 33+, guard withBuild.VERSION). The app already hasQUERY_ALL_PACKAGES, so all providers are visible.- Each row:
providerInfo.loadIcon(context, density)+ label; sort by label. - Tapping a row →
widgetHostManager.requestAddWidget(...)flow above. - Opening the picker: add a settings entry (see 4). Optionally also a long-press on empty home space if the launcher has no competing handler — check first; if home long-press is already used, use settings only.
4. Settings
Follow the existing "Notification Dots" toggle pattern
(SHOW_NOTIFICATION_DOTS in PreferenceHelper +
SettingsFeaturesFragment):
- New pref
SHOW_HOME_WIDGETS(default OFF). - Toggle: "Home screen widgets".
- When ON, expose "Add widget" (opens the picker) — e.g. a secondary row or the "+" on the home widget area.
- Add string resources in
values/strings.xmlonly (the app is on Crowdin — do not invent translations in other locales). - Pref caveat from CLAUDE.md: don't re-push the value from
observeUserInterfaceSettingsin a way that clobbers the on-disk value (see theSHOW_NOTIFICATION_DOTSnote).
5. Permissions
None new. Do NOT add android.permission.BIND_APPWIDGET (host role does not
need it). QUERY_ALL_PACKAGES already covers provider enumeration.
6. Documentation
Update CLAUDE.md in the repo root with a short section on the widget host
(service/manager name, pref key, how to place/remove a widget via adb if
useful, known quirks), matching the fork's existing documentation style.
Acceptance criteria (verify on the phone)
- Both flavors compile offline.
- Signed
withInternetAPK installs in place over the currentapp.easy.launcher(same signature, same versionCode 36) —adb install -r. - Settings → Features shows the new "Home screen widgets" toggle; enabling it surfaces "Add widget".
- The picker lists "K-9 Unread" (and other installed widgets). If K-9's
unread widget is missing, check
adb shell dumpsys package com.fsck.k9 | grep -B2 -A6 UnreadWidgetProvider— K-9's widget receivers are enabled programmatically (android:enabledfrom a manifest bool); if disabled, find the K-9 setting that enables home-screen widgets (ask the user if unclear) oradb shell pm enable com.fsck.k9/com.fsck.k9.provider.UnreadWidgetProvider. - Placing the K-9 Unread widget runs K-9's configuration (account picker), then the widget renders on the home screen showing a live unread count (currently 11 unread across the user's 3 accounts — a per-account widget shows that account's count). Reading mail in K-9 decreases it; the dot question is irrelevant here because the widget keeps counting until 0.
- Widget survives: app restart (force-stop + relaunch via HOME), and a phone reboot (after the user re-enters their PIN — CE-lock).
- Long-press → remove works; after removal the area is hidden again and the widget id is gone from prefs.
- Regression: notification dots still appear (see CLAUDE.md validation trick), home grid/weather/drawer unchanged, no crash on rotation.
- Screenshot proof:
adb exec-out screencap -p > /tmp/widget-proof.pngshowing the K-9 Unread widget on the home screen. Keep the file and report its path. - No leaked
AppWidgetHostViews:stopListeningononStop, views removed on removal. Watchlogcatfor host-related warnings.
Deliverables
- Working feature per above, committed as
feat: host app widgets on home screen (K-9 unread widget)(or similar) — commit locally, do not push. CLAUDE.mdupdated.- Verification evidence: build logs, screenshot path, list of things tested on the phone and their outcome.
Report back concisely: what you built (files + key decisions), what you verified on the phone, and anything the user must do (e.g. K-9 widget setting, PIN unlock after reboot).