Skip to content

Repository files navigation

Daily Forecast for Smartspacer

A Smartspacer plugin that brings back the Pixel "Daily Forecast" card, built from the data of the Pixel Weather (Google Weather, com.google.android.apps.weather) app.

The card on the home screen

  • In the morning — today's high / low and condition.
  • In the evening — tomorrow's high / low and condition.
  • Hidden the rest of the day. The card appears and disappears exactly on the boundaries (06:00, 10:00, 20:00 and midnight).
  • The condition icon is Pixel Weather's own, untinted, like on a Pixel.
  • Tapping the card opens Pixel Weather.

How it gets the data

Pixel Weather exposes no content provider, no broadcast and no readable service — its only first-party surfaces are system-signature. So the plugin reads the two things the app does publish, through Smartspacer's SDK:

Source Mechanism Provides
The "Forecasted weather" widget (FullForecastWidgetReceiver) SmartspacerWidgetProvider hosts the widget at 380×300dp and walks the inflated RemoteViews location, current condition + icon, today's high/low, and daily rows (weekday, high/low, icon) for the next five days
The evening "Tomorrow's forecast" notification (channel NOTIFICATION_CHANNEL_ID_WEATHER_FORECAST) SmartspacerNotificationProvider tomorrow's high/low, condition text and icon

Both feed one merged, persisted state. Widget data wins on temperatures; the notification supplies tomorrow's condition text, which the widget's daily rows do not carry.

Why the parser looks the way it does

The Pixel Weather widget is built with Jetpack Glance and the APK's resource names are stripped (0_resource_name_obfuscated), so there is no com.google.android.apps.weather:id/high_temp to look up — every view id is generated at runtime. PixelWeatherWidget therefore matches by content and shape rather than by id:

  • a temperature is anything matching ^[-−–+]?\d{1,3}\s?°[CF]?$,
  • today's high/low is the one row of exactly [temp, "·", temp],
  • a daily row is a layout whose only direct label is a weekday name (resolved in the device locale as well as English) wrapping a nested [temp, temp] pair,
  • the location is the first non-temperature text in the tree.

Glance also inserts empty FrameLayout spacers between children, so only non-blank TextViews are considered. Debug builds log the whole inflated tree under the PixelWeatherWidget tag, which is the fastest way to re-derive this if a Pixel Weather update changes the layout.

Keeping it fresh

Pixel Weather refreshes its widget roughly every 30 minutes. When the cached data is older than an hour, the plugin nudges the widget to recompose by toggling the requested widget height by one pixel — Glance only recomposes when the size actually changes.

The 06:00 / 10:00 / 20:00 / 00:00 boundaries are hit with an exact AlarmManager alarm that re-arms itself on every fire, on boot and on time/timezone changes. Smartspacer's refreshPeriodMinutes is documented as a minimum, not a schedule, so it cannot be relied on for a card that must vanish at a specific minute.

Everything is persisted to files/forecast.json, so the card survives process death; only the click PendingIntents are memory-only and fall back to a plain launch intent.

Project layout

app/src/main/java/dev/flexy/smartspacerforecast/
  ForecastWindow.kt                          time windows and the next boundary
  targets/DailyForecastTarget.kt             the Smartspacer Target (the card itself)
  widgets/PixelWeatherWidget.kt              widget host + RemoteViews parser
  widgets/WidgetRefresher.kt                 forces a recompose by toggling the size
  notifications/PixelWeatherNotification.kt  forecast-notification parser
  repository/ForecastRepository.kt           merged, persisted forecast state
  receivers/                                 window alarms, boot, manual refresh

Installing

The plugin is submitted to the Smartspacer Plugin Repository, so it can be installed from Smartspacer itself, under Plugin Repository. Because it declares com.google.android.apps.weather as its required package, it shows up in the Recommended section for anyone who has Pixel Weather installed.

Otherwise grab the APK from Releases. plugin.json in the repository root is the manifest the plugin repository reads for updates.

Build

AGP 9.2.1, Kotlin 2.4.0, compileSdk 37, minSdk 31. The Gradle daemon runs on Java 21, requested through gradle/gradle-daemon-jvm.properties, so the JDK on your PATH does not matter — Gradle finds or provisions one.

./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

Release builds are signed with the key described in keystore.properties, which is not in the repository. Without that file assembleRelease falls back to the debug key.

android.newDsl=false is set because AGP 9's new DSL is not yet compatible with the external Kotlin Gradle plugin, and the SDK requires Kotlin 2.4.

Setup on the phone

  1. Install Pixel Weather and Smartspacer, and give Smartspacer notification access.
  2. Smartspacer → Targets → add Daily Forecast. Smartspacer binds the Pixel Weather widget itself and asks to forward Pixel Weather's notifications.
  3. Optionally turn on "Weather forecasts" notifications inside Pixel Weather, so the evening card carries the condition text ("Rain showers") and not only the two temperatures.

Force a refresh from a terminal:

adb shell am broadcast -a dev.flexy.smartspacerforecast.REFRESH -p dev.flexy.smartspacerforecast

Limitations

  • The card needs Pixel Weather installed; the Target reports itself incompatible otherwise.
  • Tomorrow's condition text only appears once Pixel Weather has posted its evening forecast notification. Until then the evening card shows just the high/low from the widget.
  • Parsing depends on the Glance layout Pixel Weather renders today. A redesign of that widget will need the structure rules above revisited — the debug tree dump is there for exactly that.
  • Feature type is FEATURE_UNDEFINED on purpose. FEATURE_WEATHER makes Smartspacer merge the card into the date line as a weather complication instead of showing it as its own card.

Credits

  • Smartspacer and its plugin SDK by Kieron Quinn.
  • The widget-scraping approach follows Smartspacer's own GoogleWeatherForecastWidget and the bbcweather plugin.

About

Smartspacer plugin: a Pixel-style Daily Forecast card built from Pixel Weather's widget and notification

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages