← Calendula

A calendar for a phone without Google

You have already done the hard part. The question now is the boring one: does this app run with no Play Services, what does it ask the system for, and where does the binary come from. Short answers: yes, twelve permissions of which one is optional and four come from libraries, and F-Droid rebuilds it from source to check the binary I sign.

The dependency question

Many "privacy-friendly" apps fall over on a de-Googled phone for one of two reasons: they link Play Services for something mundane like location or maps, or they deliver notifications through Firebase Cloud Messaging and simply go quiet without it.

Calendula does neither, and there was never anything to remove. There is no play-services dependency, no Firebase, no GMS of any kind in the build; the dependency list is AndroidX (Compose, Hilt, Glance, WorkManager, DataStore), kotlinx, and the app's own shared modules. WorkManager is where the three extra permissions in the table come from. A push channel would have nothing to carry either, because a reminder is an alarm the app schedules on your device from the offsets already stored in the calendar provider. No server sends it.

I had a broader claim here about the encrypted-calendar products people land on after de-Googling, and it did not survive checking: Tuta runs its own push channel with no Play Services, and Proton Calendar schedules local alarms like everyone else. Reminders are local alarms in all of them. The Play Services dependency people actually hit is Proton's mail app, by Proton's own documentation, which is how an emailed invitation goes unnoticed rather than how a reminder fails.

What it asks the system for

Eight uses-permission lines in 2.20.4's manifest, plus four added by AndroidX libraries in the built APK. If you read permission lists before installing (and on this page you probably do), check it against the F-Droid listing, which shows the merged set and therefore four more than the manifest does. The last two rows explain the difference.

PermissionWhat for
READ_CALENDAR / WRITE_CALENDARThe entire app. Reads and writes Android’s own calendar store.
POST_NOTIFICATIONSEvent reminders. Android 13+ asks you at runtime.
RECEIVE_BOOT_COMPLETEDA reboot wipes pending alarms, including the one holding your next reminder. Without this, reminders stop after a restart.
USE_EXACT_ALARM / SCHEDULE_EXACT_ALARMA reminder at 09:50 has to arrive at 09:50. The second one is capped at Android 12 and 12L, where it is revocable and falls back to an inexact alarm if you withhold it. From Android 13 the first one covers it, and calendar apps are granted it automatically.
REQUEST_IGNORE_BATTERY_OPTIMIZATIONSUsed only to open the system dialog from the "Reliable delivery" setting. Never requested on its own.
READ_CONTACTS optionalOnly for the contact birthdays feature, only after you switch it on, and never asked for at startup. Contacts are read, never written.
WAKE_LOCK / FOREGROUND_SERVICE / ACCESS_NETWORK_STATE merged inNot mine. AndroidX WorkManager declares these and the build merges them in, which is why the store listing shows more than the manifest does. ACCESS_NETWORK_STATE reads whether a network exists; it does not permit using one, and without INTERNET no connection is possible.
DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION merged inAn internal permission declared by an AndroidX library for its own broadcast receiver. It does not allow network access.

INTERNET is not on that list. There is no such line anywhere, so the app cannot open a network connection at all. Things can still leave the phone by your own hand: sharing an event hands an .ics to whichever app you picked, and tapping a location falls back to a maps URL in your browser if nothing handles geo:. Those are your taps, not the app's traffic. You can check the permission in about ten seconds, which is the only reason the claim is worth making. The longer version of that argument is on the blog. Etar and Fossify Calendar ship without it too, so in this corner of the ecosystem it is table stakes.

Where the events come from

Calendula displays and edits; it does not sync. On a de-Googled phone that leaves two sensible setups, and they combine fine.

Contact birthdays get a line of their own, because people often miss them after leaving a Google account. Calendula can mirror birthdays, anniversaries and custom dates from your local contacts into a calendar it maintains. This happens entirely on the device and one way: contacts are read and never written.

Getting it without Play

The install page has the repository fingerprint and the one caveat worth knowing before you pick a channel: the Play build is signed with Google's key, so switching to or from it means an uninstall.

When this is the wrong app

  • Your phone is on Android 6 to 9. Calendula needs 10. Etar goes back to 6 and is the default calendar in LineageOS and CalyxOS for good reasons.
  • You want end-to-end encrypted calendar hosting. That is a server product. Calendula is a client and has no opinion about where your server is.
  • You want the most-tested option. This app is a few months old. The comparison with Etar and Fossify Calendar says plainly when one of those is the better choice.

How this was checked

Against 2.20.4 on : the permission table covers app/src/main/AndroidManifest.xml and the merged permissions shown by F-Droid. The dependency claim is the dependencies block of app/build.gradle.kts plus the version catalogue, grepped for play-services, firebase and com.google.android.gms. Zero hits. All three are in the Codeberg repository, so you do not have to take my word for either.

If something here has gone stale, mail support@jeanlucmakiola.de and I will fix it.

Frequently asked

Does Calendula need Google Play Services?

No. There is no Play Services dependency, no Firebase, and no Google Cloud Messaging anywhere in the build. The dependency list is AndroidX, Hilt, Glance and the app’s own modules. It behaves identically on a phone that has never had Play Services installed.

How do reminders work without push notifications?

They never needed push. A reminder is an alarm the app schedules on the device from the reminder offsets already stored in the calendar provider. Nothing is sent from a server, so there is nothing for FCM or UnifiedPush to deliver, which is why reminders work identically on a de-Googled phone and on a stock one.

Will it work on GrapheneOS, CalyxOS, LineageOS or /e/OS?

It is an ordinary Android app with no Google dependencies and a minimum of Android 10, so any of those on Android 10 or newer will run it. On GrapheneOS it needs no sandboxed Play Services and no special profile. On an older LineageOS build (Android 6 to 9) it will not install at all, and Etar is the right answer there.

Where do my events come from if I have no Google account?

Two options. Sync a CalDAV server (Nextcloud, Radicale, Baikal) with DAVx⁵, and those calendars appear in Calendula with no setup inside the app. Or create a device-only local calendar in Settings → Calendars and keep everything on the phone, with the built-in .ics export as your backup.

Can I verify any of this myself?

Yes. The manifest is the permission list, the Gradle files are the dependency list, and both are in the Codeberg repository. F-Droid also rebuilds the app from that source on its own machines and only publishes my APK when the two match byte for byte, so the check does not rest on my word either.