Files
agendula/app/src/main/AndroidManifest.xml
T
makiolaj 48fc7261f0 sync(chunk 2d): the account-add flow
One flow, one back-stack entry, as a stepper rather than four destinations —
the steps are not independently reachable, and "back" from the browser step
abandons a server-side flow rather than popping a screen. It hangs off Settings
with the same sliding-section pattern Storage -> Export uses.

address -> discovery -> (Nextcloud browser approval | username + password)
        -> pick lists -> add account

Provider warnings come before the attempt, not after: type a Fastmail or iCloud
address and the app password rule is stated while you type, which is the single
most common support ticket a CalDAV client inherits. Google is refused with the
reason. A login-flow host mismatch is shown, not refused — reverse proxies are
ordinary on self-hosted installs.

PreemptiveBasicInterceptor is deleted. The vendored BasicDigestAuthHandler
already sends Basic preemptively over HTTPS, also does Digest (Baikal defaults
to it, OkHttp has none), caches the working scheme, and scopes by registrable
domain — which is what iCloud's cross-host home set needs. Two implementations
of one job is the defect chunk 1 removed from ICalendarWriter.

⚠️ That handler compares its `domain` against the *registrable* domain, so
passing the full host meant credentials were withheld from every request to
every subdomain — i.e. every self-hosted Nextcloud, silently 401ing forever.
Pinned by CalDavHttpTest.

CalDavGateway and AccountCreator put the network and the database behind
interfaces so the sign-in state machine is testable without a server, a
database, a Keystore or an AccountManager. It had no tests, and the review
found eight issues in it.

Account creation is transactional and rolls its lists back explicitly:
account_id is ON DELETE SET NULL, so deleting the row alone leaves orphan
lists behind and every retry adds another set.
2026-09-04 18:37:16 +02:00

192 lines
9.1 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- External tasks-provider access, for StorageMode.EXTERNAL only. Both
permission sets are declared, since the manifest is static; the active one
(org.tasks.* for tasks.org, org.dmfs.* for OpenTasks) is requested at
runtime by the permission flow, and only once the user has actually
selected External mode. Both are dangerous-level.
StorageMode.OWN needs nothing here: it is a Room database in our own data
directory. Agendula publishes no ContentProvider and declares no
permissions of its own. -->
<uses-permission android:name="org.dmfs.permission.READ_TASKS" />
<uses-permission android:name="org.dmfs.permission.WRITE_TASKS" />
<uses-permission android:name="org.tasks.permission.READ_TASKS" />
<uses-permission android:name="org.tasks.permission.WRITE_TASKS" />
<!-- CalDAV sync. ACCESS_NETWORK_STATE is merged in by work-runtime anyway,
but it shows in F-Droid's permission diff, so declare it deliberately
rather than letting it appear from nowhere.
READ_SYNC_SETTINGS / WRITE_SYNC_SETTINGS are what the ContentResolver
sync APIs need. No FOREGROUND_SERVICE: sync is a plain worker, and the
dataSync FGS type would bring the Android 15 six-hours-per-24 budget
(whose failure mode is a fatal RemoteServiceException) and a Play
requirement for a video demo.
Two more permissions appear in the merged manifest without being
declared here, and both come from work-runtime: WAKE_LOCK, and
FOREGROUND_SERVICE. The latter is not us taking the FGS route — below
API 31 WorkManager implements expedited work with a foreground service,
and minSdk is 29, so it is load-bearing for the "Sync now" button.
Removing it with tools:node="remove" would break expedited work on
exactly the older devices that need it most. Noted because it shows in
F-Droid's permission diff and would otherwise look unexplained. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Exact due-time reminders: USE_EXACT_ALARM on 33+, SCHEDULE on 31-32. -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<!-- Package visibility (Android 11+): see the tasks providers so
resolveContentProvider works, and launchable apps so we can open the
provider / a store listing during onboarding. -->
<queries>
<provider android:authorities="org.dmfs.tasks" />
<provider android:authorities="org.tasks.opentasks" />
<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent>
<!-- Custom Tabs provider detection. Without this entry it silently finds
nothing on API 30+, and the Nextcloud login flow falls back to an
external browser for no visible reason. -->
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
<application
android:name=".AgendulaApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Agendula"
tools:targetApi="35">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Standalone crash-report surface; MainActivity routes here on a
startup crash-loop. Not exported, kept out of recents. -->
<activity
android:name=".ui.crash.CrashReportActivity"
android:exported="false"
android:excludeFromRecents="true"
android:launchMode="singleTask" />
<!-- Reminder alarm fires here (internal PendingIntent → not exported). -->
<receiver
android:name=".data.reminders.DueReminderReceiver"
android:exported="false" />
<!-- Re-arm alarms after reboot. -->
<receiver
android:name=".data.reminders.BootReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- Re-sync reminders when an external provider changes — DAVx5 pulling
tasks while Agendula is backgrounded. External mode only: in OWN mode
nothing outside the app can change our data, and Room's
InvalidationTracker covers our own writes. An intent-filter host must
be a literal, so both external authorities are listed. -->
<receiver
android:name=".data.reminders.ProviderChangeReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.PROVIDER_CHANGED" />
<data android:scheme="content" android:host="org.tasks.opentasks" />
<data android:scheme="content" android:host="org.dmfs.tasks" />
</intent-filter>
</receiver>
<!-- Sync plumbing. The stub provider exists only to give the sync
adapter an authority to register against: Agendula publishes no real
ContentProvider since :provider was deleted, and without an authority
ContentService.hasAuthorityAccess() makes every ContentResolver sync
call a silent no-op at targetSdk >= 34. -->
<provider
android:name=".data.sync.SyncStubProvider"
android:authorities="${applicationId}.sync"
android:exported="false"
android:syncable="true" />
<!-- Exported and guarded by ACCOUNT_MANAGER. Note that
android.permission.ACCOUNT_AUTHENTICATOR does not exist. -->
<service
android:name=".data.sync.AuthenticatorService"
android:exported="true"
android:permission="android.permission.ACCOUNT_MANAGER">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<service
android:name=".data.sync.SyncAdapterService"
android:exported="true"
android:permission="android.permission.BIND_SYNC_ADAPTER">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter" />
</service>
<!-- WorkManager's on-demand initialisation. Removing the default
initializer is what lets AgendulaApp supply a HiltWorkerFactory, so
@HiltWorker workers can take injected dependencies. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<!-- Persists the per-app language on API < 33, where the platform
per-app-languages API is unavailable. On 33+ this is a no-op. -->
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
</application>
</manifest>