Compare commits

...

1594 Commits

Author SHA1 Message Date
Ricki Hirner
9754770238 [CI] Fix pre-populating configuration cache 2025-12-18 10:36:29 +01:00
Ricki Hirner
6be15fd366 [CI] Actually use configuration cache (#1891)
* Cache configurations per job

* Use separate job for Dependency submission

* Use GRADLE_OPTS to enable build and configuration cache

* Test .android

* Cache .android for configuration cache

* Disable CodeQL for PRs

* Fix AVD path
2025-12-18 10:27:01 +01:00
Ricki Hirner
0cb27f0c2f [CI] Add gradle remote build cache (bitfireAT/davx5#752)
* [CI] Add gradle remote build cache

* Update workflow

* Don't cache local build cache; pre-populate configuration cache

* Allow configuration caching of tasks

* Free some disk space before running instrumented tests; cache whole .android (not only .android/avd)

* Allow branches to update configuration cache

* Use dry run to pre-populate configuration cache

* Test runs: don't cache

* Fix remote build cache configuration for non-CI builds

* Add comment
2025-12-17 18:06:17 +01:00
Sunik Kupfer
776305bd12 Rename dismissInvalidResource to dismissCollectionError (#1887)
Rename method for clarity

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-17 13:14:06 +01:00
dependabot[bot]
01f54df3c0 [CI] Bump actions/cache from 4 to 5 in the ci-actions group (#1886)
Bumps the ci-actions group with 1 update: [actions/cache](https://github.com/actions/cache).


Updates `actions/cache` from 4 to 5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 13:38:54 +01:00
Ricki Hirner
4944ce59b1 Version bump to 4.5.8-alpha.1 2025-12-12 15:32:30 +01:00
Sunik Kupfer
0e455d8371 LocalTaskList: Stop subclassing DmfsTaskList (#1882)
* LocalTaskList: Stop subclassing DmfsTaskList

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

# Conflicts:
#	gradle/libs.versions.toml

* Dont touch agp

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update synctools

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-11 16:22:02 +01:00
Ricki Hirner
a938b511cd Nextcloud Login Flow: handle non-success status codes (#1878)
* Nextcloud Login Flow: handle non-success status codes

* Update error message to use class name when localized message is null

* Update dav4jvm to get HTTP reason phrases in HttpException
2025-12-11 15:30:32 +01:00
Ricki Hirner
d32b86789b Update AGP 2025-12-11 15:13:15 +01:00
Sunik Kupfer
84d58f73db Assume initial state for test updatesOwnerAccount (#1874)
* Assume initial state

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Enhance comment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Don't pass provider

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-11 14:16:03 +01:00
dependabot[bot]
cc43998148 Bump the app-dependencies group with 4 updates (#1867)
* Bump the app-dependencies group with 4 updates

Bumps the app-dependencies group with 4 updates: androidx.activity:activity-compose, androidx.compose:compose-bom, [io.mockk:mockk](https://github.com/mockk/mockk) and [io.mockk:mockk-android](https://github.com/mockk/mockk).


Updates `androidx.activity:activity-compose` from 1.12.0 to 1.12.1

Updates `androidx.compose:compose-bom` from 2025.11.01 to 2025.12.00

Updates `io.mockk:mockk` from 1.14.5 to 1.14.7
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.7)

Updates `io.mockk:mockk-android` from 1.14.5 to 1.14.7
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.7)

Updates `io.mockk:mockk-android` from 1.14.5 to 1.14.7
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.7)

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.12.00
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk
  dependency-version: 1.14.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* Override SDK level

* Suppress lint warnings for LaunchedEffect / Context.getString

* Suppress lint warnings for other Context.getStrings (or replace by stringResource if possible)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-12-11 11:27:37 +01:00
Sunik Kupfer
b354bfebc2 LocalTask: Don't subclass DmfsTask (#1862)
* LocalTask: Don't subclass DmfsTask

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Adapt usages

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update synctools

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-10 14:37:23 +01:00
Sunik Kupfer
29240ea16f Skip flaky test when not moving into anticipated forever pending sync state (#1872)
* Assume we moved into forever pending sync state

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update comment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-10 10:25:58 +01:00
Ricki Hirner
e7b88f9aa8 Version bump to 4.5.7.1 2025-12-09 11:49:50 +01:00
Arnau Mora
4d71517cde Delegate fileName into DmfsTask.syncId (#1871)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-12-09 10:40:22 +01:00
Ricki Hirner
0d4f154baf Use Ktor for Nextcloud Login Flow (#1817)
* [WIP] Use Ktor for Nextcloud login flow

- Replace OkHttp with Ktor for HTTP requests
- Update URL handling to use Ktor's `Url` class
- Adjust `postForJson` method to use Ktor's HTTP client
- Refactor URL building logic for login flow initiation

* Use Ktor for Nextcloud login flow

- Migrate to Ktor's ContentNegotiation plugin for JSON handling
- Update dependencies and configuration for Ktor serialization
- Refactor `NextcloudLoginFlow` to use Ktor's JSON serialization

* Add tests

* Allow unit tests that mock/use HttpClient without Conscrypt

* KDoc

* Minor fixes

* Use toUrlOrNull from dav4jvm

* Don't change strings in this PR

* Update dav4jvm and synctools
2025-12-08 15:40:39 +01:00
Ricki Hirner
9eb70a5564 Bump version to 4.5.7 2025-12-08 12:19:38 +01:00
Ricki Hirner
24e0a864bd Update AVD caching in CI workflow (#1865)
- Split AVD cache handling into restore and save steps
- Add condition to save AVD cache only if restore misses
2025-12-05 16:25:25 +01:00
Ricki Hirner
10ec0c3b6d Fetch translations from Transifex 2025-12-05 15:20:46 +01:00
Ricki Hirner
bd3349cc38 Bump version to 4.5.7-rc.1 2025-12-05 15:19:43 +01:00
Ricki Hirner
c7bc2b317b Update synctools (#1864)
* Update synctools (fixes #1797, closes #1859)

* Use `com.github.bitfireAT:synctools` because `com.github.bitfireat:synctools` is not available on Jitpack for this commit
2025-12-05 15:16:29 +01:00
Sunik Kupfer
2d10cbb07d Improve closing of content provider in verify account owner test (#1838)
* Optimize imports

* Remove the ignore annotation

* Move provider use out of verify method

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove unnecessary provider.use blocks

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add spaces

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Rename lambda param provider to client in LocalDataStore implementations

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Enhance kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Improve provider client usage

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Replace calling apply with assignment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove whitespace

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add nullable returns even though they never return null

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Apply WillNotClose annotation to client parameter instead of method

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-02 10:34:39 +01:00
dependabot[bot]
88928792af Bump the app-dependencies group with 2 updates (#1860)
Bumps the app-dependencies group with 2 updates: [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) and [io.ktor:ktor-client-okhttp](https://github.com/ktorio/ktor).


Updates `io.ktor:ktor-client-core` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.2...3.3.3)

Updates `io.ktor:ktor-client-okhttp` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.2...3.3.3)

Updates `io.ktor:ktor-client-okhttp` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.3.2...3.3.3)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-core
  dependency-version: 3.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-client-okhttp
  dependency-version: 3.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.ktor:ktor-client-okhttp
  dependency-version: 3.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 18:36:53 +01:00
Sunik Kupfer
b5e8c80db1 Try to fix pending sync state test failures by using a hot flow (#1839)
* Remove the ignore annotation

* Turn inPendingState in to a hot state flow for the test duration

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Rename methods registering the sync state observer

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-12-01 14:40:52 +01:00
Ricki Hirner
6f09f55e1a Update synctools (ignores DTEND < DTSTART and inverts negative event durations) (#1858) 2025-12-01 12:02:42 +01:00
Ricki Hirner
b08f10a98f HttpClientBuilder: clarify documentation for authDomain (#1857)
* Update authentication domain parameter

- Rename `onlyHost` to `authDomain` in `fromAccount`
- Update `authenticate` method to use `domain` instead of `host`
- Clarify documentation for `authDomain` parameter

* More KDoc

* Fix other calls / tests
2025-12-01 11:54:36 +01:00
Sunik Kupfer
a3a952d875 LocalTaskList/LocalTask: Consume fields provided by synctools (#1811)
* Move companion object to end of class

* Update synctools

* Make DmfsTaskList final

* Use DmfsTaskList SyncState

* Drop fields now provided in DmfsTask and adapt constructors

* Use column constants from DmfsTask instead

* Use DmfsTask column constants

* Update synctools

* Don't handle scheduleTag

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update synctools

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-11-27 16:35:17 +01:00
Ricki Hirner
e9fc570895 Extract ResourceDownloader from ContactsSyncManager, add tests (#1849)
* Add ResourceDownloader and tests

- Introduce `ResourceDownloader` class for downloading external resources
- Add unit tests for `ResourceDownloader`
- Refactor `ContactsSyncManager` to use `ResourceDownloader`

* KDoc

- Add detailed documentation for `download` method
- Clarify authentication handling and return behavior

* Minor changes
2025-11-27 16:10:33 +01:00
Ricki Hirner
098b7d5b12 Log warning instead of throwing exception on multiple build calls (#1847)
Log warning instead of throwing IllegalStateException on multiple build calls

- Change `build()` to log a warning instead of throwing an exception on subsequent calls.
- Change `buildKtor()` to log a warning instead of throwing an exception on subsequent calls.
2025-11-27 16:08:52 +01:00
Ricki Hirner
a38dc29cca Update WebDAV property names (#1841)
* Update WebDAV property names according to new dav4jvm naming scheme

* Use new supported-report-set; fix `MaxResourceSize` property reference in CalendarSyncManager

* Remove comment
2025-11-27 11:46:10 +01:00
Ricki Hirner
84b9a14ba1 Update version to 4.5.6.1 2025-11-27 11:15:39 +01:00
Arnau Mora
cda95dc789 Fix HTTP Client provider for contact resource sync from URL (#1844)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-11-27 11:15:06 +01:00
dependabot[bot]
f64882ca2a Bump the app-dependencies group with 15 updates (#1840)
Bumps the app-dependencies group with 15 updates:

| Package | From | To |
| --- | --- | --- |
| androidx.activity:activity-compose | `1.11.0` | `1.12.0` |
| androidx.lifecycle:lifecycle-runtime-compose | `2.9.4` | `2.10.0` |
| androidx.lifecycle:lifecycle-viewmodel-ktx | `2.9.4` | `2.10.0` |
| androidx.lifecycle:lifecycle-viewmodel-compose | `2.9.4` | `2.10.0` |
| androidx.compose:compose-bom | `2025.11.00` | `2025.11.01` |
| [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp) | `5.3.0` | `5.3.2` |
| [com.squareup.okhttp3:okhttp-brotli](https://github.com/square/okhttp) | `5.3.0` | `5.3.2` |
| [com.squareup.okhttp3:logging-interceptor](https://github.com/square/okhttp) | `5.3.0` | `5.3.2` |
| [com.squareup.okhttp3:mockwebserver](https://github.com/square/okhttp) | `5.3.0` | `5.3.2` |
| androidx.room:room-ktx | `2.8.3` | `2.8.4` |
| androidx.room:room-compiler | `2.8.3` | `2.8.4` |
| androidx.room:room-paging | `2.8.3` | `2.8.4` |
| androidx.room:room-runtime | `2.8.3` | `2.8.4` |
| androidx.room:room-testing | `2.8.3` | `2.8.4` |
| [com.google.devtools.ksp](https://github.com/google/ksp) | `2.3.2` | `2.3.3` |


Updates `androidx.activity:activity-compose` from 1.11.0 to 1.12.0

Updates `androidx.lifecycle:lifecycle-runtime-compose` from 2.9.4 to 2.10.0

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.4 to 2.10.0

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.4 to 2.10.0

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.4 to 2.10.0

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.4 to 2.10.0

Updates `androidx.compose:compose-bom` from 2025.11.00 to 2025.11.01

Updates `com.squareup.okhttp3:okhttp` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:mockwebserver` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `com.squareup.okhttp3:mockwebserver` from 5.3.0 to 5.3.2
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.3.0...parent-5.3.2)

Updates `androidx.room:room-ktx` from 2.8.3 to 2.8.4

Updates `androidx.room:room-compiler` from 2.8.3 to 2.8.4

Updates `androidx.room:room-paging` from 2.8.3 to 2.8.4

Updates `androidx.room:room-runtime` from 2.8.3 to 2.8.4

Updates `androidx.room:room-testing` from 2.8.3 to 2.8.4

Updates `androidx.room:room-compiler` from 2.8.3 to 2.8.4

Updates `androidx.room:room-paging` from 2.8.3 to 2.8.4

Updates `androidx.room:room-runtime` from 2.8.3 to 2.8.4

Updates `androidx.room:room-testing` from 2.8.3 to 2.8.4

Updates `com.google.devtools.ksp` from 2.3.2 to 2.3.3
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.3.2...2.3.3)

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-runtime-compose
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.11.01
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.devtools.ksp
  dependency-version: 2.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 18:42:28 +01:00
Ricki Hirner
7c2dcf3d70 Comment out failing tests (#1836) 2025-11-24 16:00:53 +01:00
dependabot[bot]
66a34ebd9f [CI] Bump actions/checkout from 5 to 6 in the ci-actions group (#1837)
Bumps the ci-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 16:00:24 +01:00
Ricki Hirner
365364aa89 Update version to 4.5.6 2025-11-24 13:26:18 +01:00
Ricki Hirner
114543f4c5 Fetch translations from Transifex 2025-11-24 13:22:09 +01:00
Ricki Hirner
3bd3f56e1b Fix DeadObjectException handling in SyncManager and Syncer (#1834)
- Update SyncManager to handle LocalStorageException with DeadObjectException cause
- Refactor Syncer to catch all exceptions and handle specific cases
2025-11-24 13:14:40 +01:00
Ricki Hirner
5263172376 [Ktor] Add MustBeClosed annotation to buildKtor method (#1829)
Add @MustBeClosed annotation to buildKtor method

This commit adds the `@MustBeClosed` annotation to the `buildKtor` method in `HttpClientBuilder.kt` to indicate that the returned `HttpClient` instance must be closed by the caller. It also updates the test in `HttpClientBuilderTest.kt` to use the `use` function to ensure proper resource management.
2025-11-21 12:48:04 +01:00
Ricki Hirner
babd52cfb1 Bump version to 4.5.6-rc.1 2025-11-20 16:45:40 +01:00
Ricki Hirner
3d4d533b92 Update synctools (#1825)
* Update synctools

* Update commit ID
2025-11-20 16:42:40 +01:00
Ricki Hirner
76fc024ef6 Lower default minimum log level from INFO to FINE (#1827)
* Increase logging level

* Adjust log levels for visibility in non-verbose logs

- Change log level from FINER to FINE in StreamingFileDescriptor
- Update log level from FINER to FINE in AccountSettingsMigration8
- Add note about log levels in LogManager documentation

* Fix KDoc typo

* Update comment
2025-11-20 11:37:24 +01:00
Sunik Kupfer
794b4c1c7f DebugInfo: Support viewing jtx Board resources (#1818)
* Support viewing jtxBoard resources from debug info

* Correct value of EXTRA_LOCAL_RESOURCE_URI

* Correct comment

* Use the same intent for journals, notes, calendar and tasks

* State working task authorities explicitly

* Use edit action to not crash opentasks

* Use getViewIntentUriFor for jtx Board tasks

* Remove explicit tasks authority for jtx Board

* Remove explicit tasks authority for jtx Board

* Remove early return statement

* Dont handle jtxBoard tasks in LocalTask which is only for Dmfs tasks

* Add some kdoc to LocalTask and LocalJtxICalObject

* Use when with in list

* Add FLAG_GRANT_READ_URI_PERMISSION to the correct intent

* Correct line endings to from CRLF to LF
2025-11-20 09:33:24 +01:00
Ricki Hirner
aac6356722 Delete app/src/main/kotlin/at/bitfire/davdroid/network/HttpClient.kt
Remove empty file
2025-11-19 16:52:35 +01:00
Ricki Hirner
9bc46d4194 Update dependencies, including our libs (#1822) 2025-11-19 15:35:06 +01:00
Ricki Hirner
a3aac44775 Ignore failing test: testVerifySyncAlwaysPending_wrongBehaviour_android14 (#1824)
Comment out test
2025-11-19 15:14:41 +01:00
Arnau Mora
084ba3b630 Update dav4jvm to new okhttp package (#1786)
* Upgrade dav4jvm

* Exclude ktor from dav4jvm

* Fix imports and fix usages

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fix imports for instrumented tests

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fix imports

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgrade dav4jvm

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Do not exclude ktor in dav4jvm

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgrade dav4jvm

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-11-17 13:38:10 +01:00
Ricki Hirner
28dcf90775 Refactor default reminder builder to dedicated class + unit tests (#1815)
* [WIP] Move default reminder builder to dedicated class + unit tests

* Add tests

* Just turn off Conscrypt for now

* Fix library name
2025-11-17 10:34:36 +01:00
Ricki Hirner
70766affd9 [Ktor] Allow building a Ktor client (#1810)
* Add Ktor dependency

* Add buildKtor method

* Add test and deprecation notice

* KDoc
2025-11-17 09:38:08 +01:00
Ricki Hirner
d00292f421 Only use cert4android when needed (#1802)
* Refactor ClientCertKeyManager and HttpClientBuilder

- Add logging to `ClientCertKeyManager` for better error handling.
- Update `HttpClientBuilder` to conditionally use custom trust manager and hostname verifier based on `allowCustomCerts` flag.
- Rename `customCertsUI` to `allowCustomCerts` in build configuration.

* Update trust manager and hostname verifier selection logic

- Improve logging and error handling in `ClientCertKeyManager`

* App settings: hide certificate settings when custom certificates are not allowed

* Typo
2025-11-12 11:04:13 +01:00
dependabot[bot]
6b5c4f191a Bump the app-dependencies group with 2 updates (#1803)
Bumps the app-dependencies group with 2 updates: androidx.compose:compose-bom and [com.google.devtools.ksp](https://github.com/google/ksp).


Updates `androidx.compose:compose-bom` from 2025.10.01 to 2025.11.00

Updates `com.google.devtools.ksp` from 2.3.0 to 2.3.2
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.3.0...2.3.2)

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.11.00
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.google.devtools.ksp
  dependency-version: 2.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 17:28:43 +01:00
Sunik Kupfer
5c7b792e7f Support sync adapter pending sync indication on Android 14+ (#1676)
* Add AccountSettingsMigration21 to cancel pending address book syncs

* Add application context annotation

* Add log statement

* Increase account settings current version

* Add and update kdoc

* Call cancelSync via integration

* Optimize imports

* Update kdoc

* Updating log statement

* Also cancel calendar syncs

* Don't infer authority from account type

* Update kdoc

* Cancel only on Android 14+

* Cancel for all authorities and update kdoc

* Use cancelSync directly in migration

* Enable forever pending sync workaround by canceling sync adapter framework syncs on Android 14+

* Stop always returning false for pending sync state of sync adapter framework

* Cancel by request and empty bundle

* Cancel syncs for calendar, tasks, and contacts separately

* Minor edits to log statement and kdoc

* Add migration test; Update migration

* Log all extras instead of just upload flag

* Use lazy on syncFrameworkIntegration injection

* Multiple changes

- don't cancel address book accounts of all main accounts
- merge loops

* Add authority to log statement

* Replace complex state verification logic by status changed flow

* Cancel syncs account wide across all authorities

* Add some delay to allow dummy sync requests to be created

* Reduce wait until pending

* Drop Thread.sleep()

* Use a callback flow instead of mutable state flow

* Shorten first true filter

* Shorten remaining first true filter
2025-11-10 15:46:58 +01:00
Ricki Hirner
c9da496142 Bump version to 4.5.6-beta.1 2025-11-06 14:30:07 +01:00
Ricki Hirner
ee098c4a83 Explicitly integrate Conscrypt (#1796)
* Merge HttpClient and HttpClientBuilder

- Remove `HttpClient` class and replace with `OkHttpClient`
- Update all references to `HttpClient` to use `OkHttpClient`
- Add new `HttpClientBuilder` class for building `OkHttpClient` instances
- Update all builder usages to use the new `HttpClientBuilder` class

* KDoc

* Integrate Conscrypt for TLS

- Add Conscrypt dependency
- Initialize Conscrypt in HttpClientBuilder
- Create ConscryptIntegration utility

* KDoc

* Make object a class, better test

* Update cert4android to the latest version (doesn't bundle Conscrypt anymore)
2025-11-06 10:07:40 +01:00
Ricki Hirner
a8bd296520 Merge HttpClient and HttpClientBuilder (#1795)
* Merge HttpClient and HttpClientBuilder

- Remove `HttpClient` class and replace with `OkHttpClient`
- Update all references to `HttpClient` to use `OkHttpClient`
- Add new `HttpClientBuilder` class for building `OkHttpClient` instances
- Update all builder usages to use the new `HttpClientBuilder` class

* KDoc
2025-11-05 13:20:02 +01:00
Sunik Kupfer
0959624dee Update Calendars.OWNER_ACCOUNT when renaming an account (#1751)
* Fix typo

* Also set OWNER_ACCOUNT when updating calendar because renaming account

* Add test

* Update comment clarifying content values

* Assume calendar provider is present and drop null checks
2025-11-05 12:40:34 +01:00
Ricki Hirner
bd13d27e38 HttpClient: remove unnecessary close() (#1792)
* Remove unnecessary AutoCloseable implementations and client.close() calls

- Remove AutoCloseable from NextcloudLoginFlow and DavResourceFinder
- Remove client.close() calls in various classes and tests
- Update HttpClient to remove close() method

* Fix test

* Fix annotations / KDoc
2025-11-04 16:38:22 +01:00
dependabot[bot]
85548163ca Bump the app-dependencies group with 4 updates (#1790)
Bumps the app-dependencies group with 4 updates: [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp), [com.squareup.okhttp3:okhttp-brotli](https://github.com/square/okhttp), [com.squareup.okhttp3:logging-interceptor](https://github.com/square/okhttp) and [com.squareup.okhttp3:mockwebserver](https://github.com/square/okhttp).


Updates `com.squareup.okhttp3:okhttp` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:mockwebserver` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

Updates `com.squareup.okhttp3:mockwebserver` from 5.2.1 to 5.3.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.1...parent-5.3.0)

---
updated-dependencies:
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 16:07:44 +01:00
Ricki Hirner
026750eca3 Bump version to 4.5.6-alpha.2 2025-11-01 21:56:47 +01:00
Ricki Hirner
d365a504e8 Refactor sequence handling in calendar sync (#1789)
* Refactor sequence handling in calendar sync

- Move sequence update logic to SequenceUpdater
- Update LocalCalendar to use new SequenceUpdater
- Remove redundant methods from LocalCalendar
- Update tests and dependencies

* Minor KDoc
2025-11-01 21:55:28 +01:00
Ricki Hirner
c64cb1e7ec Remove okhttp caching (bitfireAT/davx5#715)
* Remove okhttp caching
* Simplification
2025-11-01 12:38:26 +01:00
Ricki Hirner
837b5e5d50 Bump version to 4.5.6-alpha.1 2025-10-30 14:29:29 +01:00
Ricki Hirner
98aefc4fee Synchronize without Event data class (#1783)
* [WIP] Proof of concept for syncing without `Event` data class

* Replace AndroidEvent2 with EventsContract

* Update synctools, refactor upload logic in `CalendarSyncManager`

* KDoc

* Update UID immediately in `ContactsSyncManager`, `CalendarSyncManager`, and `TasksSyncManager`

- Remove `OnSuccessContext.uid` from `GeneratedResource`

* Minor changes

* Handle multiple events in a single iCalendar

- Rename `processVEvent` to `processICalendar`
- Add default alarm for non-full-day events again
- Prevent NPE on null flags (used for debug info)

* Fix tests
2025-10-30 14:28:05 +01:00
Ricki Hirner
a8c8a8d2e0 Refactor SEQUENCE and UID handling on successful uploads (#1785)
* Define interfaces

* [WIP] Refactor sequence and UID handling in event uploads

- Refactor `generateUpload` method to return `GeneratedResource`.
- Update `SyncManager` to handle `GeneratedResource`.
- Implement sequence and UID management in `CalendarSyncManager`.
- Remove redundant `prepareForUpload` method from `LocalEvent`.

* Refactor sequence handling in uploads

- Move UID validation to `DavUtils.isGoodFileBaseName`
- Update sequence directly in iCalendar for group-scheduled events
- Rename `resourceBaseName` to `suggestedBaseName` for clarity

* Refactor sequence / UID handling in contact uploads

- Update `LocalAddress` interface to include `updateUid` method
- Modify `ContactsSyncManager` to handle UID generation and update
- Remove redundant UID handling in `LocalContact` and `LocalGroup`
- Adjust code style settings for right margin

* Remove redundant UID update from `ContactsSyncManager` and `CalendarSyncManager`

* Implement UID handling in `TasksSyncManager` for uploads

* Update JtxSyncManager

- Update `JtxSyncManager.kt` to implement the `generateUpload` function.
- Update `LocalJtxICalObject.kt` to include the `updateUid` method for updating UIDs in the collection.

* Remove deprecated `prepareForUpload` method from `LocalResource`

- Remove `prepareForUpload` implementations from `LocalContact`, `LocalEvent`, `LocalGroup`, and `LocalTask`

* Rename `isGoodFileBaseName` to `isGoodFileName` in `DavUtils`

* Fix tests

* Move UID generation logic to `DavUtils.generateUidIfNecessary`

- Use `DavUtils.fileNameFromUid` for generating file names
- Update `ContactsSyncManager`, `CalendarSyncManager`, `TasksSyncManager`, and `JtxSyncManager` to use new utility methods

* Add tests for DavUtils

* Some tests

* Refactor onSuccessfulUpload

* Update KDoc

* Logging

* Remove unnecessary LocalEvent method

* KDoc
2025-10-29 17:57:34 +01:00
Ricki Hirner
b839cbfe7f Simplify LocalResource interface (#1784)
* Simplify LocalResource interface

- Remove generic parameter from LocalResource interface
- Update all implementations to reflect the change
- Adjust related test cases and exception handling

* Fix tests
2025-10-29 08:50:09 +01:00
Ricki Hirner
f0f9f58e49 Bump version to 4.5.5 2025-10-28 11:20:00 +01:00
Ricki Hirner
66f6e48e3b Fetch translations from Transifex 2025-10-28 11:18:54 +01:00
Ricki Hirner
d6feda1142 Fix OkHttp3 crash in release builds (bitfireAT/davx5#712)
- Add ProGuard rules to keep OkHttp3 IDN mapping classes
2025-10-28 11:12:51 +01:00
Ricki Hirner
05f058ab3f Version bump to 4.5.5-rc.1 2025-10-27 11:16:51 +01:00
Sunik Kupfer
4e4c0f5e31 Move debug info notification action to debug info screen button (#1730)
* Update view item on sync error string

* Remove view item action from notification

* Show button in debug info screen to jump to problematic event resource

* Move companion object to the end of activity class

* Add local resource dump to intent

* Add kdoc

* Add some comments for not yet implemented resources

* Don't export DebugInfoActivity

* Send intent instead of URI and launch from DebugInfoActivity

* Add option to view problematic contact

* Extract intent builder logic to another method

* Add option to view problematic contact

* Minor changes for readability

* Extract dump string creation to interface method

* Pass Uri instead of intent and create view local resource intent in DebugInfoActivity

* Use androids existing getContactLookupUri method

* Remove extra variable

* Remove obsolete val declaration

* Rename dump to summary

* Refactor code structure for local resource URI handling

* Update code structure to use getDebugSummary for local resource summaries

* Update exception handling in SyncNotificationManager

Change the catch block to handle all `Throwable` exceptions instead of just `OutOfMemoryError`. This ensures that any potential issues arising from providing information about the local resource are caught and ignored.

* Add "copy remote URL" action

* Use string resource

* Truncate contact, task, and event strings to 1000 characters

* Fix tests

* Minor changes

- Replace `ContactsContract.RawContacts` with `RawContacts` in `LocalContact.kt`
- Remove unnecessary newline in `LocalJtxICalObject.kt`

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-10-27 11:15:21 +01:00
Arnau Mora
0304d7168a Migrate to SecureTextField (#1191)
* Using `SecureTextField`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* `OutlinedSecureTextField` doesn't support `readOnly`

* fixed string conversions

* - Update AddWebdavMountScreen to use enabled instead of readOnly
- Ensure onKeyboardAction checks canContinue before proceeding
- Update UrlLogin and EmailLogin to ensure onKeyboardAction checks canContinue before proceeding
- Update InputDialogs to ensure confirmEnabled is checked before proceeding

* Use get() for deriving things from a mutable state

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-10-27 11:15:11 +01:00
Ricki Hirner
19458aa95c Delete .github/workflows/dependent-issues.yml
Obsoleted by https://github.blog/changelog/2025-08-21-dependencies-on-issues/
2025-10-26 12:23:31 +01:00
dependabot[bot]
4412617079 Bump the app-dependencies group across 1 directory with 7 updates (#1778)
Bumps the app-dependencies group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| androidx.work:work-runtime-ktx | `2.10.5` | `2.11.0` |
| androidx.work:work-testing | `2.10.5` | `2.11.0` |
| androidx.room:room-ktx | `2.8.2` | `2.8.3` |
| androidx.room:room-compiler | `2.8.2` | `2.8.3` |
| androidx.room:room-paging | `2.8.2` | `2.8.3` |
| androidx.room:room-runtime | `2.8.2` | `2.8.3` |
| androidx.room:room-testing | `2.8.2` | `2.8.3` |



Updates `androidx.work:work-runtime-ktx` from 2.10.5 to 2.11.0

Updates `androidx.work:work-testing` from 2.10.5 to 2.11.0

Updates `androidx.work:work-testing` from 2.10.5 to 2.11.0

Updates `androidx.room:room-ktx` from 2.8.2 to 2.8.3

Updates `androidx.room:room-compiler` from 2.8.2 to 2.8.3

Updates `androidx.room:room-paging` from 2.8.2 to 2.8.3

Updates `androidx.room:room-runtime` from 2.8.2 to 2.8.3

Updates `androidx.room:room-testing` from 2.8.2 to 2.8.3

Updates `androidx.room:room-compiler` from 2.8.2 to 2.8.3

Updates `androidx.room:room-paging` from 2.8.2 to 2.8.3

Updates `androidx.room:room-runtime` from 2.8.2 to 2.8.3

Updates `androidx.room:room-testing` from 2.8.2 to 2.8.3

---
updated-dependencies:
- dependency-name: androidx.work:work-runtime-ktx
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-26 11:04:22 +01:00
Ricki Hirner
76277dbfd5 Update Kotlin and KSP versions (#1776)
- Update Kotlin version to 2.2.21
- Update KSP version to 2.3.0
- Remove specific Kotlin and KSP dependencies from Dependabot ignore list
2025-10-26 10:20:57 +01:00
Ricki Hirner
b0b99de56b Update Compose BOM and use PrimaryTabRow (#1772)
- Update Compose BOM version to 2025.10.01
- Replace `TabRow` with `PrimaryTabRow`
2025-10-25 11:15:01 +02:00
Ricki Hirner
b33c4750bb Bump version to 4.5.5-beta.1 2025-10-18 09:15:58 +02:00
Sunik Kupfer
25b749dd1b Rename account on background thread (#1727)
* Change context to default dispatcher on updating automatic sync when renaming account

* Minor changes
- add worker thread annotations
- use injected defaultDispatcher

* Add tests

* Apply withContext with default dispatcher on the whole rename method
2025-10-18 09:13:26 +02:00
Ricki Hirner
39a0fe3f98 Update AboutLibraries and other dependencies (#1760)
Update dependencies and modify AboutActivity to use dynamic library loading

- Remove outdated aboutlibraries.json
- Update AboutActivity to dynamically load libraries using LocalContext
- Replace InvalidRemoteResourceException with InvalidICalendarException in sync managers
- Bump dependency versions for various libraries including mikepenz-aboutLibraries, okhttp, and unifiedpush
- Adjust build.gradle.kts and gradle/libs.versions.toml for new plugin and library versions
2025-10-18 09:12:41 +02:00
Arnau Mora
dd798f8380 Update implementation to match guidelines (#1747) 2025-10-15 11:20:30 +02:00
Sunik Kupfer
47d380de62 Fix unreachable code possibly causing foreign key constraint violation exception (#1740)
* Add test

* Fix unreachable code possibly causing foreign key constraint violation exception

* Make code easier to understand

* Add comments

* Split up tests and add mockk verify
2025-10-14 10:57:15 +02:00
dependabot[bot]
019d32a9b7 [CI] Bump github/codeql-action from 3 to 4 in the ci-actions group (#1749)
Bumps the ci-actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3 to 4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-13 20:04:01 +02:00
Sunik Kupfer
0acabd9c80 Update http error message strings (#1745) 2025-10-13 10:41:00 +02:00
dependabot[bot]
aa23980a59 Bump the app-dependencies group across 1 directory with 13 updates (#1744)
* Bump the app-dependencies group across 1 directory with 13 updates

Bumps the app-dependencies group with 13 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| androidx.compose:compose-bom | `2025.09.01` | `2025.10.00` |
| [io.mockk:mockk](https://github.com/mockk/mockk) | `1.14.5` | `1.14.6` |
| [io.mockk:mockk-android](https://github.com/mockk/mockk) | `1.14.5` | `1.14.6` |
| [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp) | `5.1.0` | `5.2.0` |
| [com.squareup.okhttp3:okhttp-brotli](https://github.com/square/okhttp) | `5.1.0` | `5.2.0` |
| [com.squareup.okhttp3:logging-interceptor](https://github.com/square/okhttp) | `5.1.0` | `5.2.0` |
| [com.squareup.okhttp3:mockwebserver](https://github.com/square/okhttp) | `5.1.0` | `5.2.0` |
| androidx.room:room-ktx | `2.8.1` | `2.8.2` |
| androidx.room:room-compiler | `2.8.1` | `2.8.2` |
| androidx.room:room-paging | `2.8.1` | `2.8.2` |
| androidx.room:room-runtime | `2.8.1` | `2.8.2` |
| androidx.room:room-testing | `2.8.1` | `2.8.2` |
| org.unifiedpush.android:connector | `3.0.10` | `3.1.0` |



Updates `androidx.compose:compose-bom` from 2025.09.01 to 2025.10.00

Updates `io.mockk:mockk` from 1.14.5 to 1.14.6
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.6)

Updates `io.mockk:mockk-android` from 1.14.5 to 1.14.6
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.6)

Updates `io.mockk:mockk-android` from 1.14.5 to 1.14.6
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.5...1.14.6)

Updates `com.squareup.okhttp3:okhttp` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:mockwebserver` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:okhttp-brotli` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:mockwebserver` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `androidx.room:room-ktx` from 2.8.1 to 2.8.2

Updates `androidx.room:room-compiler` from 2.8.1 to 2.8.2

Updates `androidx.room:room-paging` from 2.8.1 to 2.8.2

Updates `androidx.room:room-runtime` from 2.8.1 to 2.8.2

Updates `androidx.room:room-testing` from 2.8.1 to 2.8.2

Updates `androidx.room:room-compiler` from 2.8.1 to 2.8.2

Updates `androidx.room:room-paging` from 2.8.1 to 2.8.2

Updates `androidx.room:room-runtime` from 2.8.1 to 2.8.2

Updates `androidx.room:room-testing` from 2.8.1 to 2.8.2

Updates `org.unifiedpush.android:connector` from 3.0.10 to 3.1.0

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.10.00
  dependency-type: direct:production
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk
  dependency-version: 1.14.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: io.mockk:mockk-android
  dependency-version: 1.14.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:okhttp-brotli
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: com.squareup.okhttp3:mockwebserver
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: org.unifiedpush.android:connector
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* Downgrade mockk version to 1.14.5

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-10-09 13:10:31 +02:00
Sunik Kupfer
5674d6b954 ExceptionInfoDialog: show explanation on http 405 and others (#1739)
* Show explanation on http 405

* Also show message for http 5xx

* Correct spelling
2025-10-09 12:05:31 +02:00
dependabot[bot]
df56c8628a [CI] Bump gradle/actions from 4 to 5 in the ci-actions group (#1736)
Bumps the ci-actions group with 1 update: [gradle/actions](https://github.com/gradle/actions).


Updates `gradle/actions` from 4 to 5
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v5)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: ci-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 17:08:39 +02:00
dependabot[bot]
af4ecd3a1d Bump the app-dependencies group with 12 updates (#1732)
Bumps the app-dependencies group with 12 updates:

| Package | From | To |
| --- | --- | --- |
| androidx.work:work-runtime-ktx | `2.10.4` | `2.10.5` |
| androidx.work:work-testing | `2.10.4` | `2.10.5` |
| androidx.compose:compose-bom | `2025.09.00` | `2025.09.01` |
| [com.google.dagger:hilt-android](https://github.com/google/dagger) | `2.57.1` | `2.57.2` |
| [com.google.dagger:hilt-android-compiler](https://github.com/google/dagger) | `2.57.1` | `2.57.2` |
| [com.google.dagger:hilt-android-testing](https://github.com/google/dagger) | `2.57.1` | `2.57.2` |
| [com.google.dagger.hilt.android](https://github.com/google/dagger) | `2.57.1` | `2.57.2` |
| androidx.room:room-ktx | `2.8.0` | `2.8.1` |
| androidx.room:room-compiler | `2.8.0` | `2.8.1` |
| androidx.room:room-paging | `2.8.0` | `2.8.1` |
| androidx.room:room-runtime | `2.8.0` | `2.8.1` |
| androidx.room:room-testing | `2.8.0` | `2.8.1` |


Updates `androidx.work:work-runtime-ktx` from 2.10.4 to 2.10.5

Updates `androidx.work:work-testing` from 2.10.4 to 2.10.5

Updates `androidx.work:work-testing` from 2.10.4 to 2.10.5

Updates `androidx.compose:compose-bom` from 2025.09.00 to 2025.09.01

Updates `com.google.dagger:hilt-android` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger:hilt-android-compiler` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger:hilt-android-testing` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger.hilt.android` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger:hilt-android-compiler` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `com.google.dagger:hilt-android-testing` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

Updates `androidx.room:room-ktx` from 2.8.0 to 2.8.1

Updates `androidx.room:room-compiler` from 2.8.0 to 2.8.1

Updates `androidx.room:room-paging` from 2.8.0 to 2.8.1

Updates `androidx.room:room-runtime` from 2.8.0 to 2.8.1

Updates `androidx.room:room-testing` from 2.8.0 to 2.8.1

Updates `androidx.room:room-compiler` from 2.8.0 to 2.8.1

Updates `androidx.room:room-paging` from 2.8.0 to 2.8.1

Updates `androidx.room:room-runtime` from 2.8.0 to 2.8.1

Updates `androidx.room:room-testing` from 2.8.0 to 2.8.1

Updates `com.google.dagger.hilt.android` from 2.57.1 to 2.57.2
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57.1...dagger-2.57.2)

---
updated-dependencies:
- dependency-name: androidx.work:work-runtime-ktx
  dependency-version: 2.10.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.10.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.10.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.09.01
  dependency-type: direct:production
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android-testing
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger:hilt-android-testing
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-29 22:15:39 +02:00
Arnau Mora
701e292ab5 Use IntentCompat for account fetching from intent extras (#1731) 2025-09-28 10:51:57 +02:00
Ricki Hirner
5da7d9e292 Add CODEOWNERS file (#1729) 2025-09-24 14:11:40 +02:00
Ricki Hirner
e6256764ac Bump version to 4.5.5-alpha.2 2025-09-24 14:00:30 +02:00
Ricki Hirner
e63f815416 Update synctools to work around eventStatus=null update problem (#1728)
Update synctools to work around eventStatus=null update problem; add synctools to Dependabot-ignore
2025-09-24 13:59:12 +02:00
Ricki Hirner
374dadfaaa Bump version to 4.5.5-alpha.1 2025-09-23 11:15:07 +02:00
Ricki Hirner
f6ef13f9fe Update synctools (new uses event builders/processors) and Kotlin/KSP (#1726) 2025-09-23 11:07:41 +02:00
dependabot[bot]
a88cfd2acf Bump the app-dependencies group with 4 updates (#1724)
Bumps the app-dependencies group with 4 updates: androidx.lifecycle:lifecycle-runtime-compose, androidx.lifecycle:lifecycle-viewmodel-ktx, androidx.lifecycle:lifecycle-viewmodel-compose and [com.google.guava:guava](https://github.com/google/guava).


Updates `androidx.lifecycle:lifecycle-runtime-compose` from 2.9.3 to 2.9.4

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.3 to 2.9.4

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.3 to 2.9.4

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.3 to 2.9.4

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.3 to 2.9.4

Updates `com.google.guava:guava` from 33.4.8-android to 33.5.0-android
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: androidx.lifecycle:lifecycle-runtime-compose
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.9.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: com.google.guava:guava
  dependency-version: 33.5.0-android
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-22 18:40:14 +02:00
Michael Biebl
98fc946594 Remove duplicate lines in .gitignore (#1725) 2025-09-22 18:20:50 +02:00
Ricki Hirner
00523d9bc8 Fix state matching logic in AndroidSyncFrameworkTest (#1708)
* Fix state matching logic in AndroidSyncFrameworkTest

- Add `fullMatch` parameter to control whether all expected states must be present

* Ensure non-optional expected state matches actual state

* Remove unused rule / variable

* Adapt test

- Update `onStatusChanged` to override the interface method.
- Replace custom assertion with `assertTrue` for state comparison.
2025-09-16 10:42:11 +02:00
dependabot[bot]
53d338d03e Bump the app-dependencies group with 12 updates (#1714)
Bumps the app-dependencies group with 12 updates:

| Package | From | To |
| --- | --- | --- |
| androidx.activity:activity-compose | `1.10.1` | `1.11.0` |
| androidx.hilt:hilt-compiler | `1.2.0` | `1.3.0` |
| androidx.hilt:hilt-navigation-compose | `1.2.0` | `1.3.0` |
| androidx.hilt:hilt-work | `1.2.0` | `1.3.0` |
| androidx.work:work-runtime-ktx | `2.10.3` | `2.10.4` |
| androidx.work:work-testing | `2.10.3` | `2.10.4` |
| androidx.compose:compose-bom | `2025.08.01` | `2025.09.00` |
| androidx.room:room-ktx | `2.7.2` | `2.8.0` |
| androidx.room:room-compiler | `2.7.2` | `2.8.0` |
| androidx.room:room-paging | `2.7.2` | `2.8.0` |
| androidx.room:room-runtime | `2.7.2` | `2.8.0` |
| androidx.room:room-testing | `2.7.2` | `2.8.0` |


Updates `androidx.activity:activity-compose` from 1.10.1 to 1.11.0

Updates `androidx.hilt:hilt-compiler` from 1.2.0 to 1.3.0

Updates `androidx.hilt:hilt-navigation-compose` from 1.2.0 to 1.3.0

Updates `androidx.hilt:hilt-work` from 1.2.0 to 1.3.0

Updates `androidx.hilt:hilt-navigation-compose` from 1.2.0 to 1.3.0

Updates `androidx.hilt:hilt-work` from 1.2.0 to 1.3.0

Updates `androidx.work:work-runtime-ktx` from 2.10.3 to 2.10.4

Updates `androidx.work:work-testing` from 2.10.3 to 2.10.4

Updates `androidx.work:work-testing` from 2.10.3 to 2.10.4

Updates `androidx.compose:compose-bom` from 2025.08.01 to 2025.09.00

Updates `androidx.room:room-ktx` from 2.7.2 to 2.8.0

Updates `androidx.room:room-compiler` from 2.7.2 to 2.8.0

Updates `androidx.room:room-paging` from 2.7.2 to 2.8.0

Updates `androidx.room:room-runtime` from 2.7.2 to 2.8.0

Updates `androidx.room:room-testing` from 2.7.2 to 2.8.0

Updates `androidx.room:room-compiler` from 2.7.2 to 2.8.0

Updates `androidx.room:room-paging` from 2.7.2 to 2.8.0

Updates `androidx.room:room-runtime` from 2.7.2 to 2.8.0

Updates `androidx.room:room-testing` from 2.7.2 to 2.8.0

---
updated-dependencies:
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-compiler
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-navigation-compose
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-work
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-navigation-compose
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.hilt:hilt-work
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-runtime-ktx
  dependency-version: 2.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.work:work-testing
  dependency-version: 2.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: app-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.09.00
  dependency-type: direct:production
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-ktx
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-compiler
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-paging
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-runtime
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
- dependency-name: androidx.room:room-testing
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: app-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-12 10:35:17 +02:00
Ricki Hirner
0424999225 Update dav4jvm (#1709)
- Replace `code` with `statusCode` in multiple files to align with the updated library
- Add `dav4jvm` to Dependabot ignores
2025-09-12 10:25:26 +02:00
Ricki Hirner
fa09a0560f Use SensitiveString for passwords (#1692)
* Use SensitiveString for passwords to prevent them from being logged by `toString()`

* Add test

* Fix other tests

* Credentials: equals / hashCode not needed anymore

* Add tests for equals
2025-09-10 10:31:24 +02:00
Ricki Hirner
f4aa55d482 Version bump to 4.5.4 2025-09-09 11:38:10 +02:00
Ricki Hirner
1bffd5efe1 Version bump to 4.5.4-rc.3 2025-09-06 13:14:48 +02:00
Ricki Hirner
4850f2a5a5 Update synctools to fix #1701 (#1702) 2025-09-06 13:01:56 +02:00
Ricki Hirner
53f38ce2ec Rename Dependabot dependency group 2025-09-05 11:27:06 +02:00
Ricki Hirner
7cf6e30577 Bump version to 4.5.4-rc.2 2025-09-05 10:31:21 +02:00
Ricki Hirner
cec77c33cb Fix KotlinNotImplementedError in LocalGroup update method (#1696)
- Remove scheduleTag assignment in LocalGroup update method
- Refactor LocalGroupTest to use @Before and @After annotations
- Add new test case for update method in LocalGroupTest
2025-09-05 10:27:10 +02:00
dependabot[bot]
6c98e6d501 Bump the lib-dependencies group across 1 directory with 9 updates (#1695)
Bumps the lib-dependencies group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| androidx.lifecycle:lifecycle-runtime-compose | `2.9.2` | `2.9.3` |
| androidx.lifecycle:lifecycle-viewmodel-ktx | `2.9.2` | `2.9.3` |
| androidx.lifecycle:lifecycle-viewmodel-compose | `2.9.2` | `2.9.3` |
| androidx.compose:compose-bom | `2025.08.00` | `2025.08.01` |
| [com.google.dagger:hilt-android](https://github.com/google/dagger) | `2.57` | `2.57.1` |
| [com.google.dagger:hilt-android-compiler](https://github.com/google/dagger) | `2.57` | `2.57.1` |
| [com.google.dagger:hilt-android-testing](https://github.com/google/dagger) | `2.57` | `2.57.1` |
| [com.google.dagger.hilt.android](https://github.com/google/dagger) | `2.57` | `2.57.1` |
| com.android.application | `8.12.1` | `8.13.0` |



Updates `androidx.lifecycle:lifecycle-runtime-compose` from 2.9.2 to 2.9.3

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.2 to 2.9.3

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.2 to 2.9.3

Updates `androidx.lifecycle:lifecycle-viewmodel-ktx` from 2.9.2 to 2.9.3

Updates `androidx.lifecycle:lifecycle-viewmodel-compose` from 2.9.2 to 2.9.3

Updates `androidx.compose:compose-bom` from 2025.08.00 to 2025.08.01

Updates `com.google.dagger:hilt-android` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger:hilt-android-compiler` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger:hilt-android-testing` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger.hilt.android` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger:hilt-android-compiler` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.google.dagger:hilt-android-testing` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

Updates `com.android.application` from 8.12.1 to 8.13.0

Updates `com.google.dagger.hilt.android` from 2.57 to 2.57.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.57...dagger-2.57.1)

---
updated-dependencies:
- dependency-name: androidx.lifecycle:lifecycle-runtime-compose
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-ktx
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.lifecycle:lifecycle-viewmodel-compose
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2025.08.01
  dependency-type: direct:production
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android-testing
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android-compiler
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger:hilt-android-testing
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
- dependency-name: com.android.application
  dependency-version: 8.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: lib-dependencies
- dependency-name: com.google.dagger.hilt.android
  dependency-version: 2.57.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: lib-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 10:25:31 +02:00
Ricki Hirner
a7cd1cd49f Configure Dependabot for Gradle dependencies (#1680)
* Configure Dependabot for Gradle dependencies

Add Gradle dependency management configuration to Dependabot.

* Update dependabot.yml to ignore specific Kotlin and KSP dependencies
2025-09-05 09:26:30 +02:00
Ricki Hirner
0cc84dfd01 Update synctools (#1690) 2025-09-03 16:47:40 +02:00
Ricki Hirner
87239daaf6 Fix invalid translation 2025-09-03 15:53:43 +02:00
Ricki Hirner
81ceb57842 Version bump to 4.5.4-rc.1 2025-09-03 15:41:31 +02:00
Ricki Hirner
cd0b0c0804 Fetch translations from Transifex 2025-09-03 15:41:12 +02:00
Sunik Kupfer
48cbd4a05d Disable pending sync indicator on Android 14+ (#1689)
* Disable pending sync indicator on Android 14+

* Disable forever pending sync workaround on Android 14+
2025-09-03 15:32:29 +02:00
Ricki Hirner
beccc7a0d4 Version bump to 4.5.4-beta.1 2025-09-01 11:09:52 +02:00
Sunik Kupfer
2b629c8b18 Add comments on foreign key constraint enforcement (#1675)
* Add comments on foreign key constraint enforcement

* Fix grammar

* Add foreign key constraint comment to AppDatabase

* Update kdoc with better description
2025-08-28 11:47:49 +02:00
Arnau Mora
cd725479cd Handle null cases for Events.DIRTY (#1663)
* Handled nullity of `Calendar.dirty`

* Fixed ids

* Use multiline SQL queries

* Fix kdoc links

* Improve kdoc

* Fix capitalization

* Simplify kdoc

* Improved `LocalCalendarTest`
2025-08-28 11:25:57 +02:00
Ricki Hirner
44666d2138 Add groups section to dependabot configuration (#1677)
* Add groups section to dependabot configuration

* Fix patterns syntax in dependabot.yml
2025-08-27 15:56:34 +02:00
dependabot[bot]
8e67db7d54 [CI] Bump actions/setup-java from 4 to 5 (#1672)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 10:10:21 +02:00
Sunik Kupfer
a58e3b9036 Don't disable sync-ability of address books when set to manual only (#1662)
Only disable sync on content changes; but leave syncable
2025-08-25 10:15:56 +02:00
Ricki Hirner
d63918ff42 Update dependencies to latest versions (including AGP) (#1665) 2025-08-19 16:45:27 +02:00
dependabot[bot]
f21c3de94a [CI] Bump actions/checkout from 4 to 5 (#1664)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 13:54:15 +02:00
Ricki Hirner
24d4ba65e5 Fix NPE when Event._ID is larger than Integer.MAX_VALUE (#1661)
* Add test that reproduces NPE

* Fix NPE when Event ID is larger than Integer.MAX_VALUE
2025-08-14 17:19:04 +02:00
Arnau Mora
ae96f1ffbb Using new suggested icons for tabs (#1657) 2025-08-14 10:20:08 +02:00
Arnau Mora
a08ecae635 Change "Account doesn't exist" toast message to "Account has been removed" for deleting accounts (#1650)
* Finish activity after deleting

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added proper toast for when the account is deleted

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Simplify logic

* Missing fix

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-08-13 08:55:26 +02:00
Ricki Hirner
eb4224780a Update to Gradle 9.0 (#1647) 2025-08-07 09:30:37 +02:00
Ricki Hirner
0240e67dab Bump version to 4.5.4-alpha.1 2025-08-06 16:42:10 +02:00
Ricki Hirner
0ccd9d5eb3 Update synctools for events as Entity and EventAndExceptions (#1605)
* [WIP] Refactor LocalResource interface and implementations to use immutable properties and add deleteLocal method

* Use `Optional` for `fileName` in `clearDirty` methods, update syncManagers

* Update synctools

* Refactor LocalCalendar to use AndroidRecurringCalendar for event operations

* Use AndroidCalendar.findEvent

* Update SyncManager to process fileName, eTag, scheduleTag and flags

* SyncManager: make ETag and Schedule-Tag processing more understandable

* Make upload handling more clear

* Update synctools
2025-08-06 15:38:38 +02:00
Sunik Kupfer
438f967152 [Sync Framework] Android 14+: Cancel forever pending address book account syncs (#1643)
* Also cancel sync for address books accounts

* Cancel known account directly

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-08-06 12:07:49 +02:00
Ricki Hirner
a093238864 Make "sync pending" work in AccountsScreen and AccountScreen (#1615)
* Add sync pending check for Android 14+

* Update sync pending UI logic to use selected authorities only

* Fix isSyncPending not handling multiple dataTypes

* Extract the accounts flow map to boolean flow logic

* Rename method

* Enhance kdoc

* Pass only one authority for pending check

* Update kdoc

* Update kdoc

* Update kdoc

* Fix whitespace

* Rename authority method to currentAuthority

* Update kdoc

---------

Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2025-08-06 12:04:24 +02:00
Arnau Mora
293daf1e82 Fix bottom bar color on custom tabs (#1640)
* Fix bottom bar color on custom tabs

* Removed themind

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-08-06 11:45:29 +02:00
Sunik Kupfer
3b50747ce9 Rename CollectionListRefresher to CollectionsWithoutHomeSetRefresher (#1644)
* CollectionListRefresher to CollectionsWithoutHomeSetRefresher

* Adapt tests

* Unify class/method naming

* Use "without homeset" wording when applicable

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-08-06 10:45:43 +02:00
Arnau Mora
51d6ed279a Fetch translations from Transifex (#1638) 2025-08-04 11:50:15 +02:00
Arnau Mora
2c6842ac0c Version bump to 4.5.3 (#1637)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-08-04 11:38:40 +02:00
Ricki Hirner
0e6644305a Bump version to 4.5.3-rc.2 2025-08-01 10:49:28 +02:00
Ricki Hirner
10e3b0a723 Update dependencies, including AGP (#1632) 2025-08-01 10:49:00 +02:00
Arnau Mora
2f45b705b3 Replaced Webcal tab icon in collections list (#1628)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-08-01 10:43:09 +02:00
Ricki Hirner
be6c3311d7 WebDAV: remove notifications, timeout logic (#1630)
* Refactor openDocument operation to use OsConstants for mode parsing

* RandomAccessCallbackWrapper: refactor so that it's only purpose is to avoid the memory leak

* Use main looper instead of a new thread per RandomAccessCallback

* Remove WebDAV access notification

* Remove nsk90-kstatemachine dependency

* Simplify fileDescriptor() method

* Use dedicated I/O thread again; use Kotlin `copyTo` for copying
2025-08-01 10:38:24 +02:00
Ricki Hirner
755863778b Bump version to 4.5.3-rc.1 2025-07-30 10:49:15 +02:00
Ricki Hirner
0e81866d3a Fix ETag update logic in QueryChildDocumentsOperation (#1626) 2025-07-30 10:48:39 +02:00
Arnau Mora
93a256ee75 Icons next to CalDAV/CardDAV tab titles (#1599)
* Icons next to CalDAV/CardDAV tab titles

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Use shared element transitions

* Switch to `sharedBounds` to allow font size changes

* Update app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountScreen.kt

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Minor changes

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-07-30 10:30:16 +02:00
Arnau Mora
61e9d60b7c Correctly handle SecurityException for acquireContentProvider (#1622)
* Handle `SecurityException` for `acquireContentProvider`

* Added optional `throwOnMissingPermissions` arg to `acquireContentProvider`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Set `throwOnMissingPermissions` to `true`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Adapt comments, remove now unnecessary try/catch in AccountSettingsMigration20

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-07-30 10:07:54 +02:00
Ricki Hirner
dc9fb7b608 AndroidSyncFrameworkTest: allow optional states (#1625)
* Update sync state verification to allow optional states

* Refactor AndroidSyncFrameworkTest to use stateEquals for comparison
2025-07-30 09:42:16 +02:00
Ricki Hirner
44b52f65a2 [WebDAV] Implement command pattern, streamline lifecycle, remove WebdavScope (#1617)
* Remove WebdavScope as it is no longer needed

* [WIP] DavDocumentsProviderImpl

* [WIP] Move DavDocumentsProvider to BaseDavDocumentsProvider and implementation to DavDocumentsProvider

* Adapt tests and DI

* [WIP] Implement Command pattern

* Finish Command pattern, add deprecation notices

* Unify DavDocumentsProvider with wrapper again

* Get rid of DavDocumentsActor

* Add notes about lifecycle, remove shutdown
2025-07-29 10:17:05 +02:00
Sunik Kupfer
e13c140554 Extract refreshHomesetsAndTheirCollections to HomeSetRefresher (#1606)
* Extract refreshHomesetsAndTheirCollections to HomeSetRefresher

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Minor changes

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-07-28 09:59:40 +02:00
Ricki Hirner
cdb50205f4 Don't warn on newer Commons versions 2025-07-24 18:12:06 +02:00
Sunik Kupfer
2ba4a2a510 Version bump to 4.5.3-alpha.1 2025-07-24 14:07:12 +02:00
Sunik Kupfer
38b2377760 Follow redirects when adding webdav mount (#1582)
* Follow redirects when adding webdav mount

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update dav4jvm version

* Remove unused null

* Fix tests

* Add Kdoc

* Add Kdoc

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-07-24 14:02:50 +02:00
Sunik Kupfer
10f6356a6e Extract refreshPrincipals() to PrincipalsRefresher (#1607)
* Extract refreshPrincipals to PrincipalsRefresher

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Make method public

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-07-24 13:49:23 +02:00
Ricki Hirner
df4b6d3fbc Use early entry point for sync adapter services (#1610) 2025-07-24 13:19:56 +02:00
Ricki Hirner
dab948730e Choose real or fake (for tests) SyncAdapter over DI (#1608)
* Choose real or fake (for tests) SyncAdapter over DI

* Minor changes

* Rename SyncAdapterServicesTest.kt to RealSyncAdapterTest.kt

* Group sync adapter / sync framework classes into new package

* Cache SyncAdapter in SyncAdapterServices

* Add documentation to SyncAdapter interface and rename RealSyncAdapterTest to SyncAdapterImplTest
2025-07-24 12:19:54 +02:00
Sunik Kupfer
288583bfad Extract discoverHomesets() to ServiceRefresher (#1604)
* Extract discoverHomesets to ServiceRefresher

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Move hiltRule to top; Add space

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Log every request with method and path

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-07-24 11:33:48 +02:00
Sunik Kupfer
98c0b0c36a [Sync framework] Fix sync always pending on Android 14+ (#1463)
* Android 14 and 15 workaround: tell the sync framework to cancel any pending syncs

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add test which documents wrong pending sync check behaviour

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Exclude android 13 and below

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Cancel only own sync request

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Cancel only after enqueuing sync worker

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Move test to AndroidSyncFrameworkTest

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Reset master sync state

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove limited parallelism and increase test timeout

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Rename test method

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add assert message

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update comment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add sdk suppress annotation

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use runBlocking to be able to catch the timeout exception

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Extract pending sync cancellation to method

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Enhance sync framework tests for Android versions 9 to 16, verifying correct and incorrect pending states

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add tests for sync always pending behavior on Android 10 and 11

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove obsolete unmockkAll call

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Make tests a bit more reliable

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Extract cancelSync method call to utils to stub the call more easily

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove some unnecessary calls and update stub

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update expected states lists

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Move cancelSyncInSyncFramework to SyncFrameworkIntegration

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Pass the whole sync extras bundle when cancelling sync

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* [WIP] Initialize pending sync state reporting wrong behaviour

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Optimize SyncAdapterServicesTest

* Remove unused property

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Reset master sync state

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Revert "Reset master sync state"

This reverts commit 4bfe73a25a.

* Revert "Remove unused property"

This reverts commit 7c0fdbf392.

* Reapply "Reset master sync state"

This reverts commit 5f7f0f9bce.

* Reapply "Remove unused property"

This reverts commit f1d5009f8a.

* Increase timeout to 2 min

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* [WIP] Optimize tests

* Optimize sync framework tests

* SyncAdapterServices FakeSyncAdapter: support interrupting

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-07-23 13:03:02 +02:00
Sunik Kupfer
ed7a477d3f [UI] Use "(optional)" in labels to indicate optional textfields (#1571)
* Clarify optional fields in UI

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add icons to add webdav mount screen

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add title to group mount point address and name

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use assistant composable

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-07-22 09:20:06 +02:00
Ricki Hirner
b0609fafb2 Update synctools for AndroidEvent2 (#1601)
* [WIP] Update synctools

* Refactor LocalEvent to use LegacyAndroidCalendar for event operations

* Refactor LocalEvent to use LegacyAndroidCalendar for event operations

* Update cert4android to get 16 kB page size support over Conscrypt 2.5.3 (#1581)

* Move SyncState to resource package because it's not in the database (#1585)

* Update dependencies, including dav4jvm that updates okhttp to 5.x (#1593)

* Update dependencies, including dav4jvm that updates okhttp to 5.x

* Update mockk and okhttp

* Bump version to 4.5.2-beta.1

* Move Insert/update to DAO (#1587)

* Move homeset insert/update logic from repository to DAO; add thread-safety test

* Rename insertOrUpdateByUrlRememberSync

* Fix exceptions being fetched as `Parcelable` instead of `Serializable` in `DebugInfoActivity` (#1597)

Typo: replace `getParcelableExtra` with `getSerializableExtra`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Bump version to 4.5.2

* Fetch translations from Transifex

* Add documentation and handle missing event in LocalEvent

* Minor changes

* Rename `event` to `getCachedEvent()` to make it more clear what it does

* Update SEQUENCE after successful event upload more explicitly

* Update sequence after successful calendar event upload

* Remove deprecated add() method from LocalResource

* Update KDoc

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Arnau Mora <arnyminerz@proton.me>
2025-07-22 09:15:10 +02:00
Ricki Hirner
94a85833bc Bump version to 4.5.2.1 2025-07-21 18:13:31 +02:00
Ricki Hirner
4c5c8c3ed0 Don't disable AndroidX startup completely (#1603)
* Add test

* Update AndroidManifest.xml to configure Hilt/WorkManager integration and remove default WorkManagerInitializer
2025-07-21 18:09:37 +02:00
Ricki Hirner
4685ab6d0c Revert "Update synctools" (#1600)
Revert "Update synctools (#1579)"

This reverts commit 62a0ba3520.
2025-07-21 11:57:34 +02:00
Ricki Hirner
62a0ba3520 Update synctools (#1579)
* [WIP] Update synctools

* Refactor LocalEvent to use LegacyAndroidCalendar for event operations

* Refactor LocalEvent to use LegacyAndroidCalendar for event operations

* Update cert4android to get 16 kB page size support over Conscrypt 2.5.3 (#1581)

* Move SyncState to resource package because it's not in the database (#1585)

* Update dependencies, including dav4jvm that updates okhttp to 5.x (#1593)

* Update dependencies, including dav4jvm that updates okhttp to 5.x

* Update mockk and okhttp

* Bump version to 4.5.2-beta.1

* Move Insert/update to DAO (#1587)

* Move homeset insert/update logic from repository to DAO; add thread-safety test

* Rename insertOrUpdateByUrlRememberSync

* Fix exceptions being fetched as `Parcelable` instead of `Serializable` in `DebugInfoActivity` (#1597)

Typo: replace `getParcelableExtra` with `getSerializableExtra`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Bump version to 4.5.2

* Fetch translations from Transifex

* Add documentation and handle missing event in LocalEvent

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Arnau Mora <arnyminerz@proton.me>
2025-07-21 11:54:20 +02:00
Ricki Hirner
71f3558b4b Fetch translations from Transifex 2025-07-21 10:50:15 +02:00
Ricki Hirner
22d933096f Bump version to 4.5.2 2025-07-21 10:24:22 +02:00
Arnau Mora
666b707854 Fix exceptions being fetched as Parcelable instead of Serializable in DebugInfoActivity (#1597)
Typo: replace `getParcelableExtra` with `getSerializableExtra`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-07-21 10:03:19 +02:00
Ricki Hirner
39f6b82926 Move Insert/update to DAO (#1587)
* Move homeset insert/update logic from repository to DAO; add thread-safety test

* Rename insertOrUpdateByUrlRememberSync
2025-07-21 09:28:32 +02:00
Sunik Kupfer
b02fd23f0a Bump version to 4.5.2-beta.1 2025-07-17 11:26:40 +02:00
Ricki Hirner
ca56380c29 Update dependencies, including dav4jvm that updates okhttp to 5.x (#1593)
* Update dependencies, including dav4jvm that updates okhttp to 5.x

* Update mockk and okhttp
2025-07-17 11:06:04 +02:00
Ricki Hirner
ba9eb1446b Move SyncState to resource package because it's not in the database (#1585) 2025-07-16 12:06:34 +02:00
Ricki Hirner
055599c74f Update cert4android to get 16 kB page size support over Conscrypt 2.5.3 (#1581) 2025-07-15 09:50:39 +02:00
Ricki Hirner
62db3da579 Update Android Gradle Plugin to 8.11.1 and AboutLibraries to 12.2.4 2025-07-14 10:58:22 +02:00
Ricki Hirner
76d8d5acbf Sync davx5 / davx5-ose repos (#1575)
* Add custom certs UI build config field and use it in HttpClient; sync CollectionDao

* Backport max accounts setting
2025-07-10 16:49:07 +02:00
Ricki Hirner
dd294a4b03 Move external URIs from Constants to ExternalUris object (#1574)
* [WIP] Move external URIs from Constants to ExternalUris object

* Update external URIs to use class names or screen name for stat params
2025-07-10 16:14:09 +02:00
Sunik Kupfer
0efe6a7b9b Bump version to 4.5.2-alpha.1
Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-07-10 11:49:25 +02:00
Ricki Hirner
405b7abb39 [synctools] Use EventReader / EventWriter (#1573)
* Update synctools. Use Writer instead of ByteArrayOutputStream

* Update synctools
2025-07-10 10:57:45 +02:00
Ricki Hirner
4e2640ca01 Synctools: AndroidEvent companion object moved (#1572)
* Update synctools

* Refactor LocalCalendar to use Hilt

* Update synctools
2025-07-09 16:35:27 +02:00
Sunik Kupfer
904c8ba29b [Sync framework] Disable contacts content change triggered syncs if sync interval set to manual only (#1569)
* Fix lint error

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Show manual sync interval setting in UI

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Disable contacts content change triggered syncs if set to manual; update kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update comments and kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Automatically close provider

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Explicitly handle special case

* Rename updateAutomaticSync to updateSyncFrameworkSetting; adjust comments

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-07-09 11:13:54 +02:00
Michael Biebl
62dc73c2a0 Use system defined line separator (#1562)
This fixes the test suite on Windows.
Windows uses CR LF, in contrast to Linux which uses LF. So do not
hard-code the value.
2025-07-07 17:42:24 +02:00
Ricki Hirner
58344099f7 AndroidCalendar refactoring (#1560)
* [WIP] Refactor calendar sync manager to use synctools library

* [WIP] Update synctools

* [WIP] Tests

* Remove test logger module and update calendar color methods

* Fix migrations

* Update libs.versions.toml
2025-07-03 22:12:21 +02:00
Ricki Hirner
b62c7eff0b Update to synctools version that uses explicit parsing / splitting (#1554)
* Update to synctools version that uses explicit parsing / splitting

* Update synctools
2025-07-02 15:33:13 +02:00
Ricki Hirner
12cedd4010 Fetch translations from Transifex 2025-07-02 14:30:57 +02:00
Ricki Hirner
3a0221c749 Update version to 4.5.1 2025-07-02 14:30:07 +02:00
Ricki Hirner
f78e7868e8 Revert "Update to synctools version that uses explicit parsing / splitting (#1554)"
This reverts commit 5dbaedfa60.

Reason: we don't want it in 4.5.1 yet
2025-07-02 14:28:57 +02:00
Ricki Hirner
5dbaedfa60 Update to synctools version that uses explicit parsing / splitting (#1554)
* Update to synctools version that uses explicit parsing / splitting

* Update synctools
2025-07-01 21:52:20 +02:00
Ricki Hirner
6187f92efd Bump version to 4.5.1-rc.2 2025-06-29 16:33:51 +02:00
Ricki Hirner
82ccf6a2f9 Update workflows to use build cache; don't reduce error level for configuration cache problems anymore 2025-06-29 16:23:56 +02:00
Ricki Hirner
0f9c5027d4 Fetch translations from Transifex 2025-06-29 15:42:40 +02:00
Ricki Hirner
7b76df3e70 Bump version to 4.5.1-rc.1 2025-06-29 15:41:36 +02:00
Ricki Hirner
80cfe1013d [CI] Don't generate a new discussion thread for every release
Updates will be posted in major release thread manually
2025-06-29 15:28:23 +02:00
Ricki Hirner
3e3c346019 Update dav4jvm 2025-06-29 14:58:12 +02:00
Ricki Hirner
1773dff8a4 Update synctools for R8 rules 2025-06-27 21:54:14 +02:00
Ricki Hirner
604b0aab98 Bump version to 4.5.1-alpha.1 2025-06-27 17:44:04 +02:00
Ricki Hirner
35cffa603b LocalCalendar: don't subclass AndroidCalendar (#1552)
* [WIP] Refactor LocalEvent to delegate to AndroidEvent

* Move tests

* Use test rules from synctools

* Add null check for content provider client in JtxSyncManagerTest

* Update dependencies, move OkhttpClientTest

* Refactor LocalCalendar to wrap AndroidCalendar

* Update bitfire-synctools to 1a613d5d3c
2025-06-27 17:43:14 +02:00
Ricki Hirner
89c3eacd36 LocalEvent: don't subclass AndroidEvent (#1551)
* [WIP] Refactor LocalEvent to delegate to AndroidEvent

* Move tests

* Use test rules from synctools

* Add null check for content provider client in JtxSyncManagerTest

* Update dependencies, move OkhttpClientTest
2025-06-27 17:06:41 +02:00
Ricki Hirner
4246ed65ac OAuth: Synchronize access token generation (#1547)
* OAuthInterceptor: synchronize refreshing of access token

* Remove sensitive logging

* [WIP] Logging

* [Google] Support custom client ID on re-authorization

* Statically synchronize acquisition of access token

* [WIP] OAuth: use callbacks for reading/writing AuthState

* Fix DavResourceFinderTest

* Move Credentials class to settings package; KDoc

* Simplify reauthorization
2025-06-27 11:24:02 +02:00
Ricki Hirner
789e7f3045 Set iCalendar PRODID in Constants and use it in respective classes (#1550)
* Set iCalendar PRODID in Constants and use it in respective classes

* Update bitfire-synctools
2025-06-26 15:38:37 +02:00
Michael Biebl
66f99f7362 Update README.md after switch to synctools (#1549) 2025-06-26 12:50:27 +02:00
Sunik Kupfer
90b04ddbdc [Google] Remove warning box on login screen (#1548)
* Remove the google login warning card

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove unused uri handler variable

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-06-26 12:47:53 +02:00
Ricki Hirner
a7f8ea8a48 Don't subclass AndroidEvent / AndroidCalendar populate / build methods anymore (#1544)
* Fix tests

* Update synctools; use AndroidCalendar SyncState

* Update synctools; move companion objects to end of class declarations
2025-06-25 14:17:58 +02:00
Sunik Kupfer
42cd8d8631 Update sync progress bar when pending in SAF (#1445)
* Provide flow to check if sync of account and authority is pending in sync framework

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Show if sync is pending in sync framework

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Show if sync is pending in sync framework

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Fix kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Cancel any pending SAF syncs on sync request

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Cancel sync adapter sync only after having enqueued our worker sync

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Improve accuracy by also checking isSyncActive

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove log statements

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Only query pending state

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Cancel sync adapter sync only on android 14 and 15

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Cancel sync adapter sync with authority

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Cancel sync adapter sync by using sync request instead of canceling directly

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Include android 16

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Include all versions after Android 14

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add test which documents wrong pending sync check behaviour

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Revert "Add test which documents wrong pending sync check behaviour"

This reverts commit 8c538149ff2cb032d6355232c1736e103dcc9a18.

* Drop Android 14+ always pending sync work around

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Differentiate better between enqueued and pending syncs

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Refactor sync pending check to use flow for address book accounts

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Always return false for sync pending state check on Android 14+

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Refactor sync pending check to use flow for address book accounts

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add comments

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update comment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Shorten variable name

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update comments and variable name

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remvoe obsolete call and add argument names as comments

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove sync active check from listener

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-06-25 13:54:44 +02:00
Ricki Hirner
a26847cf10 Update AGP, Kotlin, dependencies 2025-06-24 23:00:19 +02:00
Ricki Hirner
0e6c26aec6 Update synctools and use unified BatchOperation (#1543)
* Update synctools and use unified BatchOperation

* Fix tests
2025-06-24 16:26:40 +02:00
Arnau Mora
2204027993 Improve IME integration on forms (#1504)
* Fix focus accessibility issues

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Got rid of focus options. Improved IME integration

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Remove custom focus requesters

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Move url text to top of fields

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Add focus requester again

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved text

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-06-24 11:36:07 +02:00
Arnau Mora
e7189d66b0 Link to Managed DAVx5 in navigation drawer (bitfireAT/davx5#633)
Added managed drawer entry
2025-06-24 09:43:48 +02:00
Ricki Hirner
c517647819 Update dependencies 2025-06-24 09:40:21 +02:00
Ricki Hirner
0780b226ff Add F-Droid changelog 2025-06-23 13:20:54 +02:00
Ricki Hirner
8d9a417753 Use Android 14 for instrumented tests again (#1535) 2025-06-23 10:51:07 +02:00
Ricki Hirner
008c314b80 Version bump to 4.5 2025-06-23 09:34:10 +02:00
Ricki Hirner
f0019c54b1 Fetch translations from Transifex 2025-06-23 09:33:35 +02:00
Ricki Hirner
51ad6ee00a Intro / battery optimizations warning: remove Samsung; change wording from "probably" to "may" (#1532)
* Intro / battery optimizations warning: remove Samsung; change wording from "probably" to "may"

* Remove Huawei from list
2025-06-20 12:15:43 +02:00
Ricki Hirner
dd453a7837 Update version to 4.4.12-rc.1 2025-06-16 13:17:46 +02:00
Ricki Hirner
f91c968eb8 Update synctools, unify local storage exception handling 2025-06-16 13:16:18 +02:00
Ricki Hirner
968a43f9cc Support OAuth re-authorization (#1521)
* [WIP] Add OAuth re-authorization flow

* Refactor authorization contract to OAuthIntegration

* Add OAuth re-authentication UI and logic

* Update KDoc and UI strings
2025-06-12 09:37:59 +02:00
Sunik Kupfer
ac965b411b Delete local collection if missing remotely at delete request (#1512)
* Inject IO dispatcher

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Delete local collection on delete request when not remotely present anymore

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-06-11 11:13:35 +02:00
Michael Biebl
1608384418 Redact Authorization header from HTTP requests (#1520)
* Redact Authorization header from HTTP requests

https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor/README.md

https://github.com/bitfireAT/davx5-ose/discussions/1438

* Use constant HttpHeaders.AUTHORIZATION from com.google.common.net.HttpHeaders

https://guava.dev/releases/19.0/api/docs/com/google/common/net/HttpHeaders.html#AUTHORIZATION

* Redact Cookie headers as well
2025-06-10 15:13:28 +02:00
Ricki Hirner
f25c22eba5 Replace ical4android and vcard4android with synctools (#1519) 2025-06-10 13:32:46 +02:00
Ricki Hirner
b1f742fb3a Update dependencies to latest versions 2025-06-05 20:10:09 +02:00
Ricki Hirner
47c8a0589d Bump version to 4.4.12-alpha.1 2025-06-05 20:08:16 +02:00
Arnau Mora
7c6474ce91 Fix edge to edge issue (#1515)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-06-05 11:54:48 +02:00
Ricki Hirner
de8c1d160d Support Fastmail OAuth (#1509)
* Add Fastmail OAuth login implementation

* [CI] Run tests on API level 36, too

* Add Fastmail OAuth login support

* Remove logging and move companion object to bottom

* Remove FastmailLogin and GoogleLogin to OAuthLogin and OAuthGoogle

- Remove FastmailLogin class
- Refactor GoogleLogin class to OAuthGoogle object
- Update AndroidManifest.xml to use ${applicationId} for OAuth redirect URI
- Add OAuthFastmail object for Fastmail OAuth integration
- Update GoogleLoginModel and FastmailLoginModel to use OAuthGoogle and OAuthFastmail respectively
- Add OAuthIntegration object for shared OAuth functionality

* Update Fastmail authentication error message and add redirect URI documentation

* Add error handling for refresh token exception
2025-06-05 11:54:14 +02:00
Ricki Hirner
fa50fe4c30 [CI] Run tests on API level 35 2025-06-04 16:14:00 +02:00
Arnau Mora
ba4d3b2fd1 Increase SDK to 36 (#1513)
* Upgrade to SDK 36

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Enable on back invoked callback

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Remove `enableOnBackInvokedCallback`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-06-04 15:42:57 +02:00
Sunik Kupfer
0fed85fdc3 Add app password hint under password field (#1507)
* Add app password hint under password field

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Change text to use prefer

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Append path encoded

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update app password help URL and text

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-06-04 15:42:40 +02:00
Sunik Kupfer
6fbaea9487 [SyncManager]s Remove authority (#1491)
* Update sync stats to store sync data type instead of authority

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use a real authority in the tests

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Replace authority with syncDataType in sync managers

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Minor changes
- import index
- edit comments

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use lowercase localised strings for datatypes

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Pass sync data type extra as string

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove unknown datatype

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Move datatype name strings to collection screen section

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update string usages

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update test

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add any type annotations to arrays

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-06-04 14:44:12 +02:00
Arnau Mora
fc2bc8aa47 Pass state to modal drawer for automatic back handler (#1495)
* Pass state to modal drawer for automatic back handler

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fix deprecation: Use toUri instead of Uri.parse

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2025-06-02 17:12:41 +02:00
Ricki Hirner
0321e4ab8f [Lint] Convert URIs to strings using toUri() (#1506) 2025-06-02 12:00:13 +02:00
Ricki Hirner
711543c5f1 Credentials / dav4jvm: store passwords as CharArray (#1483)
* Credentials / dav4jvm: store passwords as CharArray

* Fix tests
2025-05-30 17:37:14 +02:00
Ricki Hirner
5c485834e9 Update Gradle wrapper and Android Gradle Plugin versions 2025-05-30 17:36:10 +02:00
Ricki Hirner
f349f1fec8 Bump version to 4.4.11 2025-05-30 16:28:23 +02:00
Ricki Hirner
e6413506cb Fetch translations from Transifex 2025-05-30 16:26:53 +02:00
Michael Biebl
d4b5039297 Use lowercase GroupIDs as a workaround for jitpack.io issues (#1489)
Related discussion at

https://github.com/bitfireAT/davx5-ose/discussions/1236

and

https://github.com/jitpack/jitpack.io/issues/3295#issuecomment-2329134019

For example trying to access:
https://jitpack.io/com/github/bitfireAT/dav4jvm/b87d772e44/ I get:

File not found. Build ok

In comparison under
https://jitpack.io/com/github/bitfireat/dav4jvm/b87d772e44/ I get:

build.log
dav4jvm-b87d772e44-sources.jar
dav4jvm-b87d772e44.jar
dav4jvm-b87d772e44.module
dav4jvm-b87d772e44.pom
dav4jvm-b87d772e44.pom.md5
dav4jvm-b87d772e44.pom.sha1
2025-05-26 13:53:21 +02:00
Ricki Hirner
979f2257de Bump version to 4.4.11-rc.2 2025-05-22 19:43:03 +02:00
Ricki Hirner
3efb8d5c62 TaskSyncer: accept nullable ResyncType (#1487)
Fix TaskSyncer constructor to accept nullable ResyncType
2025-05-22 19:42:34 +02:00
Ricki Hirner
ec657519a9 Bump version to 4.4.11-rc.1 2025-05-22 17:33:24 +02:00
Sunik Kupfer
a835557b35 Handle when AccountActivity is started without account (#1481)
* Pass new account as non-nullable

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Move companion object

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Handle missing account in intent by logging and redirecting to accounts overview

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Fix deprecation

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Also check account exists

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Simplify toast message

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add account name to toast message

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Don't check for invalid accounts in AccountActivity; handle InvalidAccountException in model

* Minor changes

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-05-22 17:22:31 +02:00
Arnau Mora
19f86670bf Implemented sort order for DAV documents (#1434)
* Implemented sort order

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed column name

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Implemented sort order

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed column name

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Improved issues

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Add test for WebDavDocumentDao.getChildren for ORDER BY

* Converted getChildren into a raw query

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fix formatting of SQL query in WebDavDocumentDao

* Refactoring

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Drop comment

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed default sort to show directories first

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed tests

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Switched to query constructor instead of in-place

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed log method

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Add DocumentSortByMapper to handle orderBy mapping for WebDavDocument queries

* Rename DavDocumentsProviderTest to DocumentSortByMapperTest and update test method name

* Refactor sorting and mapping

* Add "order by name" as last criterion

* Remove default sort order constant from DocumentSortByMapper and use WebDavDocumentDao.DEFAULT_ORDER instead

* Adapt comments

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-05-22 09:48:12 +02:00
Ricki Hirner
f74d14e2a2 Always append one-time syncs (#1482)
* Refactor sync worker manager to always append one-time syncs (but only once)

* Adapt KDoc
2025-05-21 15:57:10 +02:00
Ricki Hirner
57ef059099 [CI] Also run tests on push to main branch 2025-05-21 11:59:12 +02:00
Ricki Hirner
f157a819b7 Bump version to 4.4.11-beta.1 2025-05-21 11:55:51 +02:00
Ricki Hirner
9bbc4c096d Update Compose BOM version to 2025.05.01 2025-05-21 11:55:51 +02:00
Sunik Kupfer
b306219015 [Push] Append sync when already syncing (#1480)
* [WIP] Add delay to sync worker and append push syncs

* Enqueue sync work with append policy for push notifications

* Remove delay

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Also check for enqueued work

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-05-21 11:54:18 +02:00
Ricki Hirner
469c30b511 [CI] Run tests on pull requests instead of git pushes (#1479)
* [CI] Run tests on pull requests instead of git pushes

* Add comment to PR trigger in test-dev.yml

* Update concurrency group for development tests
2025-05-21 11:30:24 +02:00
Sunik Kupfer
05f6c7ab0b Reset subscription URLs on distributor change (#1467)
* Move distributor set/get to PushRegistrationManager

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Handle unsubscription after manual distributor change before resubscribing

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Minor changes

* Update distributor check to use method of the own class

* Use mutex and add KDoc

* UnifiedPush registration: add service type to "message for distributor"

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
Co-authored-by: Arnau Mora <arnyminerz@proton.me>
2025-05-19 16:42:40 +02:00
Ricki Hirner
eeb94d4039 [Sync] Replace extras Bundle by explicit arguments (#1475)
* Remove extras parameter from sync managers

* Refactor sync worker parameters and improve documentation

* Adapt tests

* Rename resyncType to resync in syncers, adapt KDoc

* Remove legacy sync result comment
2025-05-19 15:37:50 +02:00
Ricki Hirner
7bf9172bdc SyncManager: directly wrap HTTP calls with runInterruptible (#1472)
* SyncManager: Handle CancellationException in sync process

* Wrap remote resource operations with runInterruptible; provide suspending SyncException wrappers

* Update more sync methods to use runInterruptible only for the HTTP request itself

* Make downloadRemote methods suspendable and use runInterruptible for better concurrency handling

* Update code style

* Fix tests

* Refactor SyncException to use runBlocking for wrapping resources
2025-05-19 12:54:51 +02:00
Arnau Mora
dec5be5690 Display FCM instead of DAVx⁵ in distributor settings (#1468)
* Display FCM instead of DAVx⁵ in distributor settings

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Update app/src/main/kotlin/at/bitfire/davdroid/ui/AppSettingsScreen.kt

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-19 10:49:19 +02:00
Ricki Hirner
d8e8129d7b Bump version to 4.4.11-alpha.1 2025-05-14 13:49:05 +02:00
Ricki Hirner
c9fb7dc7a2 Update dependency 2025-05-14 13:46:47 +02:00
Arnau Mora
ededcb98e1 Added widget with just a sync icon (#1340)
* Renamed existing widget

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added sync icon widget

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added labels for widgets

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added widget preview

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed max width restriction

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed widget description

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-05-14 08:50:50 +02:00
Arnau Mora
fc10a315d5 Added warning about credentials in debug info (#1457)
* Added warning about credentials in debug info

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Updated warning message

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed descriptive icon

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Replace "alert" by "notice"

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-05-13 15:45:52 +02:00
Ricki Hirner
cfeb6b3974 Make SyncManager suspending (first part) (#1451)
* Introduce coroutine dispatcher for sync operations

* Make `logSyncTimeBlocking` and `insertOrReplace` suspend functions

* Suspend first bunch of SyncManager methods; move sync dispatcher usage to SyncManager

* [WIP] Fix tests

TODO: extract test framework changes to separate PR

* Remove mainDispatcher from SyncManagerTest

* Remove explicit coroutineScope naming
2025-05-12 15:52:54 +02:00
Ricki Hirner
a15902e586 [DI] Make backing field of TestDispatcher + scheduler private 2025-05-10 09:17:22 +02:00
Ricki Hirner
47afddbd08 Simplify running suspending tests (#1452)
* Move LoggerModule to di package and add TestLoggerModule

* Remove main dispatcher injection and use runTest directly

* Add verbose logging module for tests

* Add test for UnifiedPushService.onUnregistered
2025-05-09 14:55:30 +02:00
Ricki Hirner
5f647b7403 Move GetDelayUntil tests to dav4jvm 2025-05-09 12:18:16 +02:00
Ricki Hirner
d460e4ca7b Update dav4jvm 2025-05-09 11:50:45 +02:00
Ricki Hirner
827a1b954f Update version code and name and increase max build metaspace size to 1GB 2025-05-09 10:59:34 +02:00
Ricki Hirner
54bcda1bb4 Update Android Gradle Plugin and Compose BOM versions 2025-05-08 13:27:13 +02:00
Ricki Hirner
7003c5f730 Bump version to 4.4.10-rc.1 2025-05-07 18:03:28 +02:00
Ricki Hirner
4ccf99ce23 Fetch translations from Transifex 2025-05-07 18:03:03 +02:00
Ricki Hirner
3fca4d60f1 Update dependencies 2025-05-07 18:02:57 +02:00
Ricki Hirner
2099f47d22 Use ical4android that removes ClassLoader checks; refactor SyncDispatcher (#1446)
* Use ical4android that removes ClassLoader checks; refactor SyncDispatcher

* [Tests] Add SyncDispatcher provider for tests

* Add sync dispatcher with fixed thread pool

* Replace fixed thread pool context with thread pool executor (as we had it previously, but without setting the contextClassLoader)

* Replace sync dispatcher with I/O dispatcher with limited parallelism
2025-05-07 17:57:46 +02:00
Sunik Kupfer
c12a723a52 Send Push-Dont-Notify header (#1444)
* Update dav4jvm

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add push-dont-notify header to requests when push subscription has not expired

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Fix mocks in TestSyncManager

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use relaxed mockk for collection

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use lazy val for pushDontNotifyHeader

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use null instead of empty map for pushDontNotifyHeader

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Get push subscription state as calculated collection property

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add tests for active push subscription status in collection

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Rename test methods

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Return active subscription or null

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use empty map instead of null for pushDontNotifyHeader

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Fix test

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Always send push-dont-notify header if subscription exists

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Fix copyright

* Send Push-Dont-Notify URL as quoted string

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-05-07 17:22:54 +02:00
Arnau Mora
eb0b75a9a7 [Push] Better title/description for Google FCM in UI (#1424)
* Added FCM indicator

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed text

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added content description

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Add link to manual for WebDAV push in app settings

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-05-02 12:06:23 +02:00
Arnau Mora
94ca9cd871 Fixed deprecations with menu anchors (#1428)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-04-30 11:58:01 +02:00
Ricki Hirner
9f697f06be Repositories: apply new naming scheme (#1423)
* DavHomeSetRepository, DavServiceRepository: apply new naming scheme for (non-)suspending calls

* AccountRepository: apply new naming scheme for (non-)suspending calls

* Update repository methods to new naming scheme
2025-04-30 10:58:00 +02:00
Ricki Hirner
ba7f95aad5 Clean up app/build.gradle (#1421)
* Remove packaging resources exclusion and configurations

* Update mikepenz-aboutLibraries to 12.1.0-rc03 and configure resource merging for LICENSE files
2025-04-27 11:10:20 +02:00
Ricki Hirner
993fffaa15 Fix UnifiedPushService tests (#1420) 2025-04-27 10:21:17 +02:00
Ricki Hirner
4b7f7ed45e Fetch translations from Transifex 2025-04-27 10:03:44 +02:00
Ricki Hirner
7e80607a34 Bump version to 4.4.10-beta.1 2025-04-27 10:01:41 +02:00
Ricki Hirner
d0389f13fc Move Hilt modules to .di package (bitfireAT/davx5#665)
Move Hilt module definitions to di package
2025-04-26 23:21:27 +02:00
Sunik Kupfer
993d0f66ec [Push] Update subscriptions when collection is (un)selected for sync (#1407)
* Update subscriptions when collection is (un)selected for sync

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove collections change listener and its hilt module

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove observer pattern to listen for collection changes

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update push subscriptions when collection is (un)selected for sync

* Update collection selection listener to use lazy initialization

* Update push subscriptions and sync when collection is (un)selected for sync

* Update app/src/main/kotlin/at/bitfire/davdroid/ui/CollectionSelectedUseCase.kt

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Move CollectionSelectedUseCase to account package

* Add test for CollectionSelectedUseCase

* Inject application scope instead of using a factory

* Update tests to run on our main dispatcher

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-26 22:13:40 +02:00
Arnau Mora
4c9ad959dd Upgrade about libs to 12.1.0-rc02 (#1416)
* Upgrade about libs to 12.1.0

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Update AboutLibraries version to 12.1.0-rc02

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-04-26 14:43:51 +02:00
Ricki Hirner
044a28138a [Push] Don't block main thread in UnifiedPushService (#1412)
* Refactor UnifiedPushService to use CoroutineScope so that it doesn't block on main thread

* Add KDoc why the scope is not cancelled
2025-04-25 10:36:02 +02:00
Ricki Hirner
e8ec98c257 Fix HttpClient.Builder.followRedirect 2025-04-25 10:30:53 +02:00
Ricki Hirner
fe0c1e67e7 Drop LiveData (#1414)
* Remove LiveData dependencies
2025-04-25 09:59:08 +02:00
Ricki Hirner
7261a8137d [IDE] Code style, width 2025-04-25 09:05:43 +02:00
Ricki Hirner
b1493f3f6a Bump version to 4.4.10-alpha.2 2025-04-24 20:08:30 +02:00
Ricki Hirner
d679dc5e97 [Push] Fix various subscription problems (#1411)
* Handle non-existing account gracefully when (un)subscribing collections

* Unsubscribe from all (subscribed) collections when no push distributor is selected
2025-04-24 20:03:39 +02:00
Ricki Hirner
b0f7196f2b Fix non-suspending DB access on main thread in accountRepository.delete() (#1409)
Make more database queries suspendable and run them in a blocking context
2025-04-24 19:02:22 +02:00
Ricki Hirner
77a6e5c5ab Bump version to 4.4.10-alpha.1 2025-04-24 13:12:38 +02:00
Ricki Hirner
845d979046 Update dependency versions 2025-04-24 13:12:14 +02:00
Arnau Mora
f62509ed80 [Push] Support UnifiedPush Connector 3.x, VAPID, Encryption, Google FCM (#1325)
* Upgrade UnifiedPush Connector to 3.0.4

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Updated overrides

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added storing keys and auths

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Excluded tink

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed deprecations and calls

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Integrate UnifiedPush 3.x connector and FCM distributor

* Integrate UnifiedPush connector 3.x, use VAPID and message encryption

* [WIP] Refactor push registration logic and remove deprecated methods

* [WIP] Remove PushRegistrationWorkerManager and refactor PushRegistrationManager

* Remove unused service repository dependency and update worker to suspend

* Add suspend modifier to DAO methods and repository methods

* Add runBlocking to getByService call in CollectionListRefresherTest

* Add documentation for UnifiedPushService and PushRegistrationManager

* Add fallback for push messages without topic

* [WIP] Add UnifiedPushService test with workaround for PushService binder

* Update UnifiedPush library version and clean up test code

* Refactor push message handling, synchronization and coroutines

* Add coroutine dispatchers for push registration and unregistration

* Add async support for push subscription updates

* Refactor unsubscribe logic into reusable method

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-04-24 13:09:51 +02:00
Ricki Hirner
e79c362f46 [WebDAV] Refactor coroutine usage (#1405)
* Inject coroutine scopes for cancellation and I/O

* Inject coroutine scopes for better cancellation and lifecycle management

* Inject coroutine scopes for WebDAV operations

* Fix tests

* More correct coroutine contexts
2025-04-24 12:12:17 +02:00
Arnau Mora
5c35741226 Update AboutLibraries to 12.0.0 (#1406)
* Upgrade aboutlibs to 12.0.0

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Migrated code

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Apply AboutLibraries after other gradle plugins

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-04-24 10:33:59 +02:00
Ricki Hirner
b90b8ce6a2 Update AGP to 8.9.2 2025-04-23 17:30:29 +02:00
Sunik Kupfer
321aeedd8f Sync after sync flag or forceReadOnly flag of a collection are changed (#1383)
* Perform delayed sync on a collection UI change

- sync state
- force-read-only state

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use withContext to access DB on background thread

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Remove return comment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Extract delay value to constant

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use accountRepository to create account from name

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-04-23 17:25:12 +02:00
Arnau Mora
09f68a237b NPE when trying to log in without specifying an email (#1401)
* Added check for null baseUri

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Rollback

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added check for IME next

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-04-22 10:44:21 +02:00
Ricki Hirner
93d715bb99 Test coroutines with runTest instead of runBlocking (#1399)
Add kotlinx-coroutines-test dependency and replace runBlocking with runTest in tests
2025-04-22 10:44:08 +02:00
Arnau Mora
04fe8e1aca Hide "keep permissions" in API 29 and below (#1400)
* Hide "keep permissions" for API 29 and below

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fix deprecation

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-04-22 09:35:12 +02:00
Ricki Hirner
1f02f3cc27 Use R8 full mode (#1397)
* Use R8 full mode

* Add dav4jvm dependency (which includes an XmlPullParser) for testing
2025-04-20 12:53:17 +02:00
Ricki Hirner
a0acd4e929 Use ProGuard rules from libraries (#1387)
Update library versions and optimize ProGuard rules (most of them are now in the libraries consumer rules)
2025-04-20 12:46:46 +02:00
Ricki Hirner
3901e6ebe4 Update translation fetching script to use relative paths 2025-04-20 10:30:11 +02:00
Ricki Hirner
f229226521 Bump version to 4.4.9 2025-04-20 10:26:36 +02:00
Ricki Hirner
6644e4acd7 Fix 4.4.9 release compile error (#1386)
Update ical4android version and force Apache Commons dependencies
2025-04-17 16:39:23 +02:00
Ricki Hirner
d46f8056a5 Fetch translations from Transifex 2025-04-17 12:55:02 +02:00
Ricki Hirner
c3731ace88 Version bump to 4.4.9-rc.1 2025-04-17 12:53:21 +02:00
Ricki Hirner
038c2df524 Update dependencies 2025-04-17 12:52:20 +02:00
Arnau Mora
400318b390 Fixed paddings on horizontal mode (#1353)
* Fixed paddings

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added padding to all screens

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed edge to edge on accounts screen

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed e2e issues

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Undo edge-to-edge

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Rollback

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed paddings

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed padding consumption

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Got rid of ime padding

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed issues with paddings

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fix UI elements partially obscured in landscape mode

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-04-17 12:47:59 +02:00
Arnau Mora
0d2e5a1f07 Avoid NPE when missing DTSTART for recurring events (#1336)
* Added handling of null dtstart

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added JtxSyncManagerTest

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Test recurrence id without dtstart does not cause NPE

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Extract syncmanager creation from try-catch

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add tests

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Assert RRULE remains in main vtodo

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Skip tests when jtx board not installed

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Correct annotation

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Simplify null checks

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Extract recurid definition

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update ical4android

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Find recurrence instance without dtstart

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Rename method for clarity and update kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use new method in test too

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Fix lint warnings

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use a custom rule to wrap and ignore security exception if jtxboard is not installed

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Use existing permission utils

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Rename capture to catch exceptions rule

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Format code

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2025-04-16 14:49:44 +02:00
Ricki Hirner
9835cd0d53 Refactor SharedPreferences edits to use Kotlin extension function (#1382) 2025-04-15 11:46:17 +02:00
Bernhard Stockmann
f6d8efcd26 Update Screenshots (#1380)
now reflects latest UI
2025-04-14 13:32:27 +02:00
Ricki Hirner
955de83b35 Add VAPID support to Collection entity (#1376) 2025-04-13 11:41:30 +02:00
Ricki Hirner
29a09f2038 Update dependencies, including dav4jvm (#1374)
* Update dependencies

* Remove unused import and simplify topic extraction

* Fix room issue
2025-04-13 10:26:34 +02:00
Ricki Hirner
fff332f31f Enable dependabot for Github Actions (#1363) 2025-04-01 17:28:24 +02:00
Ricki Hirner
b4d4a2fddd Update Android Gradle Plugin and Compose BOM versions 2025-04-01 10:40:56 +02:00
Arnau Mora
566a539a85 Debug Info does not contain actual error (#1362)
Fixed typo on intent extra

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-04-01 10:20:03 +02:00
Arnau Mora
e588ada891 Original string for webdav_add_mount_empty_more_info broken (#1354)
Got rid of extra `</string>` tag inside of CDATA

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-03-23 11:36:16 +01:00
Sunik Kupfer
0c89e3ba3b Version bump to 4.4.8 2025-03-13 12:44:38 +01:00
Sunik Kupfer
7b0e134c20 Fetch translations from Transifex 2025-03-13 12:40:43 +01:00
Ricki Hirner
8f7c285cb7 Move InvalidAccountException to account package (#1342)
Move InvalidAccountException
2025-03-13 11:21:44 +01:00
Arnau Mora
a2cddfc012 Release 4.4.8-rc.1 (bitfireAT/davx5#656)
* Increase version code and modify version name

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-03-13 11:21:44 +01:00
Sunik Kupfer
54eaecc6b5 Backport of bitfireAT/davx5#655: honor build config decision on whether users are allowed to accept custom certificates
* Honor customCertsUI build config value on whether users are allowed to accept custom certificates

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Add kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Update comment

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

* Extract config evaluation; Update kdoc

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>

---------

Signed-off-by: Sunik Kupfer <kupfer@bitfire.at>
2025-03-11 12:37:55 +01:00
Sunik Kupfer
0012dec482 Track app foreground state via AppTheme using separate object (#1335)
* Track app in foreground state via AppTheme using separate object

* Make inForeground read only; add kdoc

* Remove type
2025-03-05 11:13:41 +01:00
Ricki Hirner
ced6abea3f Update AGP and dependencies 2025-03-05 10:41:35 +01:00
Sunik Kupfer
4a82baeaea Remove special chars in address book account names (#1334)
* Remove special chars from address book account names

* Add a test

* Update kdoc

* Only remove iso control and SQL problematic chars
2025-03-05 09:51:39 +01:00
Sunik Kupfer
f41b4fd59d Show a notification at sync when ContentProvider inaccessible (#1278)
* Extract notification handling to separate class

* Notify user at sync if content provider is missing

* Dismiss only content provider specific notification

* Remove title from notification text body

* Move sync warning strings into their own block

* Add KDoc, duplicate method for clarity

* Show message in notification for disabled tasks apps

* Pass authority through method calls

* Shorten method names

* Don't show content provider error notification when missing permission

* Rename methods and remove obsolete var

* Add spacing in content provider missing warning

* Improve kdoc

* Remove obsolete tasks provider error messages

* Syntactic sugar

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-03-04 13:36:29 +01:00
Ricki Hirner
f6bd4b0fc2 Update issue templates to support issue types and allow blank issues (for internal use) 2025-03-01 12:47:36 +01:00
Ricki Hirner
50879b6a0c Update AGP, Kotlin, dependencies 2025-02-27 13:14:47 +01:00
Arnau Mora
6be42d4ec3 Added timestamp to debug info (#1323)
* Added timestamp to debug info

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added timestamp to `DebugInfoActivity.IntentBuilder`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Show local time and UTC of timestamp

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-02-25 20:00:22 +01:00
Sunik Kupfer
a56d42d9a5 Provide test address books via DI (#1310)
* Add LocalTestAddressBookStore.kt

* Update tests acquiring local test address books

* Remove unused methods from LocalTestAddressBook

* Extract dirty check methods to the single test class using them

* Remove unused read only flag

* Drop obsolete context

* Reusables as properties

* Rename LocalTestAddressBookStore to LocalTestAddressBookProvider

* Minor changes

* Remove test address book after provision finishes and don't remove all before

* Fix more tests by removing address book accounts after run, not before

* Wrap provision method call in try-finally

* Rename provide methods anonymous function param for clarity

* Extract account recreation to variable

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-02-25 17:27:45 +01:00
Ricki Hirner
e34952bca9 Update dependencies 2025-02-19 12:58:36 +01:00
Arnau Mora
9d293a00e7 Added confirmation dialog for "Distrust System Certificates" (#1307)
* Added confirmation dialog for dsc

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Automatic dialog dismiss

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added preview, and changed texts

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Updated dialog

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-02-15 23:04:32 +01:00
Arnau Mora
1ee41f8027 Use TaskStackBuilder instead of PendingIntent.getActivity (#1293)
* Use `TaskStackBuilder` instead of `PendingIntent.getActivity`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Replaced usages for addNextIntentWithParentStack

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Remove unused import

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-02-13 18:53:10 +01:00
Sunik Kupfer
d3c1dbb5da DnsRecordResolverTest: Use seeded random number generator for deterministic test result (#1306)
* Use seeded random number generator for a 100% deterministic test result

* Fix typo

* Rename variable

* Mock random numbers and assert corresponding record is found
2025-02-12 17:50:50 +01:00
Sunik Kupfer
cd554d885b Skip login type selection when logging in via intent (#1267)
* Move companion object to the end of class

* Skip login type selection when logging in via intent

* Skip login type page if not default login type

* Add test for implicit email intent

* Fix test

* Update KDoc

* Refactor URI handling in LoginActivity and StandardLoginTypesProvider

* Skip login type page if intent is clear, but don't skip when using defaultLoginType

* Log unclear intents

* Use data class instead of pair

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-02-11 10:58:15 +01:00
Ricki Hirner
d9b4149d41 Refactor: replace deprecated getShowOnlyPersonalPair method and simplify removeAccount logic (#1304) 2025-02-07 11:55:16 +01:00
Arnau Mora
4af6165094 Backport login_credentials_lock for password change in account settings (bitfireAT/davx5#643)
---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-02-07 11:41:47 +01:00
Ricki Hirner
fb2023762d Add syncActive flag to control SyncAdapter behavior during tests (#1302) 2025-02-07 11:26:10 +01:00
Ricki Hirner
946c450036 SyncAdapter: Hilt error handling (#1299)
* SyncAdapter: Hilt error handling

* HiltTestRunner: enforce Android P requirement for MockK

* Update comment
2025-02-06 17:29:14 +01:00
Ricki Hirner
969d92d037 Bump version to 4.4.7 2025-02-02 18:30:35 +01:00
Ricki Hirner
6998f009c4 Fetch translations from Transifex 2025-02-02 14:57:15 +01:00
Ricki Hirner
cba1f01bdb Version bump to 4.4.7-rc.1 2025-02-02 14:56:29 +01:00
Ricki Hirner
5f80c8e779 Add migration for Syncer URL → ID change (#1285)
* Add AccountSettingsMigration20 to handle collection ID migration for syncer

* Add success path tests for address books and calendars

* Increase CURRENT_VERSION, fix task list store
2025-02-02 14:55:39 +01:00
Ricki Hirner
5ece438b3f Open-source intro page: don't select a "dont show for... months" option by default (#1287)
Refactor RadioButtons component to support initial selection and update parameter names for clarity
2025-02-02 14:46:50 +01:00
Ricki Hirner
acd4e41f8b Update cert4android to avoid Conscrypt crash in custom certificate handling (#1290) 2025-02-02 14:35:36 +01:00
Ricki Hirner
ae08093906 Syncer SecurityException: disable permission notification for now (#1284) 2025-02-02 14:28:07 +01:00
Ricki Hirner
dd456b41f1 Version bump to 4.4.7-beta.1 2025-01-31 22:52:19 +01:00
Ricki Hirner
30283f36a4 Update dependencies 2025-01-31 22:51:53 +01:00
Ricki Hirner
4858dd9229 Move authority and ContentProvider creation to LocalDataStore (#1272)
* Move authority and content provider acquire() to LocalDataStore implementations

* Fix tests

* Update LocalCalendarStore.kt

Reroder LocalCalendarStore delete
2025-01-30 12:42:11 +01:00
Sunik Kupfer
b910ba25ae Use ID to match DB collections with content provider collections (#1274)
* Update ical4android

* Match DB collections with content provider collections via ID

* Minor renaming and KDoc

* Move string constant to companion object

* Update KDoc

* Use getOrDefault to be more explicit

* Remove exception throw on missing collection ID

* Rewrite LocalAddressBookStoreTest

* Minor changes
- remove unused param
- make companion methods internal

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-01-29 16:47:51 +01:00
Arnau Mora
2a542210ca MKCALENDAR does not send valid calendar-timezone (#1251)
Fixed missing calendar properties

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-01-28 12:39:24 +01:00
Sunik Kupfer
eef85f1f7a Provide three options for donation reminder time (#1256)
* Provide three options for donation reminder time

* Move radio buttons to top card; Change strings

* Use padding instead of height for radio elements
2025-01-27 17:50:12 +01:00
Ricki Hirner
4f2d4e3a49 Further unmockk after tests 2025-01-27 16:47:31 +01:00
Ricki Hirner
feccb76ce8 MockK: use MockkRule if possible (#1269)
Unmockk all
2025-01-27 16:24:44 +01:00
Ricki Hirner
835689a4a6 Use DI for HttpClient.Builder (#1250)
* [WIP] Use Hilt for HttpClient.Builder

* Inject Provider<HttpClientBuilder> when necessary

* Proxy support

* [WIP] Tests

* Fix tests

* Minor changes, enable cache support again

* Update HttpClient to use modern TLS connection specification (disable TLS 1.0 and 1.1)

* Use real CredentialsStore
2025-01-27 13:44:33 +01:00
Ricki Hirner
50cbac147e [CI] Use if: !cancelled() instead of if: always() (#1266)
To make jobs cancellable,
see https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#always
2025-01-27 13:02:38 +01:00
Ricki Hirner
d1dd2f016e Fetch translations from Transifex 2025-01-25 12:54:34 +01:00
Ricki Hirner
bd5e26a9a9 Version bump to 4.4.6 2025-01-24 17:55:54 +01:00
Sunik Kupfer
2d686bee01 Show a warning if calendar or contacts storage is deactivated or missing (#1243)
* Add AppTheme to previews

* Show warning when contacts or calendar system apps are missing or disabled

* Change android icon to database missing icon

* Remove duplication

* Use packageChangedFlow to observer live changes

* Send user to settings app when deactivated and manual when missing

* Find whether content provider app is available by authority

* [WIP] Minor changes

* Open "Manage apps" instead of manual

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-01-24 17:52:17 +01:00
Arnau Mora
2438f1a8d4 Replaced LocalContext casting with LocalActivity (#1261)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-01-22 21:17:00 +01:00
Ricki Hirner
331f8d5743 Bump version to 4.4.6-rc.1 2025-01-22 15:53:40 +01:00
Ricki Hirner
7e43524ff5 Update dependencies 2025-01-22 15:53:00 +01:00
Arnau Mora
708d94b69b Debug info: print all workers (#1200)
* Added printing of debug info for AccountsCleanupWorker

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Improved docs

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed issues

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Cleanup

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Move debug info generation into separate class

* Minor changes

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-01-22 15:27:28 +01:00
Arnau Mora
8b3c36f702 Use DB StringDefs also in DAOs and methods where they are used (#1252)
* Added annotations to daos

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added missing annotations on methods

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Missing annotation

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2025-01-22 11:35:37 +01:00
Sunik Kupfer
ca2d57cf61 Change warning when Internet is not available (#1255) 2025-01-22 11:33:58 +01:00
Sunik Kupfer
1cc9b4bdd1 Fix show only personal setting not updating the view immediately (#1238)
* Split pair of show only personal settings

* Create and consume showOnlyPersonal settings separately

* Fix showOnlyPersonal flow state change not triggering re-emission

* Combine if statements

* Minor refactoring (lift out "if")

* Create separate reload methods

* Reload on model creation

* Use viewmodel scope

* Move init after relevant method declarations

* Add kdoc

* Remove deprecated getShowOnlyPersonalPair

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-01-20 13:45:50 +01:00
Sunik Kupfer
226583f19e Don't remove port from URI of nextcloud login intent (#1242)
* Test loginFromIntent

* Fix given URI

* Add test

* Use URI authority directly

* Add test for implicit intents

* Add URI port only if not -1

* Modify URI using Java URI

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-01-16 14:34:51 +01:00
Ricki Hirner
f3333b7b54 Update AUTHORS and copyright notices (#1232)
* Update AUTHORS

* Add Android Studio copyright profiles

* Update copyright notices
2025-01-10 16:41:37 +01:00
Sunik Kupfer
226560230d Collection list refresh: Don't update fetched homesets (#1222)
* Collection list refresh: Don't update home sets that have been fetched already

* Expand testDiscoverHomesets for personal flag

* Add comment

* Rename property; Update its kdoc

* Make class properties function params

* Extract home set class and property definitions for home set discovery

* Pull out HomeSetClassName and property names

* Minor KDoc changes

* Move collection and principal query properties

* Make properties private

* Make collectionProperties service-specific; drop unused SupportedAddressData

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-01-10 15:34:48 +01:00
Sunik Kupfer
6a08497b3a Make room entity properties immutable (#1218)
* Make entity properties immutable where possible

* Make WebDavDocument room entity fully immutable

* Make HomeSet room entity fully immutable

* Make Collection room entity fully immutable

* Minor change

* KDoc, use transaction for combined read/write access

* Minor changes

---------

Co-authored-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-01-10 15:02:51 +01:00
Ricki Hirner
356183084f Update AGP, dependencies 2025-01-10 14:11:08 +01:00
Ricki Hirner
5ea7273c94 Update dependencies 2025-01-09 15:29:49 +01:00
Sunik Kupfer
843013a0f0 Use StringDef to annotate possible service and collection types (#1227)
* Add StringDef annotation to collection type param

* Add StringDef annotation to service type param
2025-01-09 12:08:11 +01:00
Ricki Hirner
ac8de37b6f Remove test account type (#1224)
* [WIP] Remove "test account" account type

* Fix tests
2025-01-08 16:49:12 +01:00
Sunik Kupfer
62dc374774 Drop address books authority (#1217)
* Remove obsolete method

* Remove address_books_authority

* Don't use contacts authority needlessly
2025-01-07 12:39:16 +01:00
Ricki Hirner
1f83e1bf12 Version bump to 4.4.6-beta.1 2025-01-03 14:13:56 +01:00
Arnau Mora
4c9b67a9e5 Rename account with jtx collection: IllegalArgumentException (#1198)
* Moved account renaming to `LocalTaskListStore` and trying to fix issue

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed missing import

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Improved renaming algorithm

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added `asSyncAdapter`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Split account renaming responsibility

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Split account renaming responsibility

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of unused `SettingsManager`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Simplified updateAccount on LocalDataStore

* Added explanatory comment

* Changed provider acquiring to the store one

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* LocalTaskListStore takes provider name instead of authority

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Got rid of throws

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Simplified expressions

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added renaming of calendar accounts

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed imports

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved calls to try-catch

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Typo

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Ignore exceptions of every store.updateAccount()

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2025-01-03 10:09:27 +01:00
Ricki Hirner
4cbe03b351 Hide sync entries in system accounts (#1214)
Hide sync entries in system settings; don't allow to manage accounts over system accounts anymore
2025-01-01 19:02:30 +01:00
Ricki Hirner
365f87991a DB: move migrations to separate files and use DI (#1206)
* DB: move migrations to separate files

* Use Hilt for AutoMigrationSpecs

* Tests in separate package

* Use Hilt for explicit Migrations
2024-12-31 16:24:50 +01:00
Ricki Hirner
77a795dfe5 Use bundleOf and contentValuesOf, if applicable (#1204)
Use bundleOf and contentValuesOf if applicable
2024-12-30 11:33:25 +01:00
Arnau Mora
794007fa38 [Push] Upon notification, only enqueue sync for the respective service type (#1175)
* Added enqueuing of the correct authority

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using new `SyncDataType`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Refactoring

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added VEVENT check

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Style updates

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-12-29 12:31:17 +01:00
Arnau Mora
1e17e1883b Trimmed URLs for URI generation (#1202)
Trimming URLs for uri generation

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-12-28 14:14:09 +01:00
Ricki Hirner
48ecb5e008 Bump version to 4.4.6-alpha.1 2024-12-26 18:38:32 +01:00
Ricki Hirner
f503ce5ff6 Sync workers: use data type enum instead of specific authority (#1177)
* Introduce SyncDataType for Workers

* Fix tests, remove SyncWorkerManager.enqueueOneTime(authority)

* TasksAppManager.currentProviderFlow(): return Flow instead of StateFlow

* Simplify TasksAppManager and TasksAppWatcher

* [WIP] AutomaticSyncManager

* [WIP] AutomaticSyncManager

* AccountSettings: optimize imports

* SyncWorkManager: remove deprecated methods

* AutomaticSyncManager: disable unused authorities in sync framework

* Add migration draft

* AccountSettings: minor changes

* Migration, BaseSyncWorker: use sync data type

* Tests, set default sync interval when tasks app is installed, notify on missing tasks app permission during sync

* Remove deprecated AccountSettings methods

* AccountSettings: actually increase version number

* Use automaticSyncManager.updateAutomaticSync where applicable; better handle manual sync interval

* KDoc

* Remove deprecated SyncWorkerManager.syncAuthorities; fix cancelAllWork

* AccountSettings: minor changes

* TasksAppManager: show notification on missing permissions; always update automatic syncs

* AutomaticSyncWorker: only provide updateAutomaticSync() as public method

* AccountSettings: simplify setSyncInterval

* AutomaticSyncManager: disable automatic task sync when no tasks provider is available
2024-12-26 18:34:09 +01:00
Ricki Hirner
98578feeb2 TestUtils: add common method to initialize WorkManager for instrumentation tests 2024-12-25 20:05:37 +01:00
Ricki Hirner
0762cc6c27 AccountSettings: allow to create new instances during migrations (#1195)
AccountSettings: allow to create new instances during migrations
2024-12-25 19:50:40 +01:00
Ricki Hirner
b267291e93 TasksAppManager: use Flow instead of StateFlow 2024-12-25 12:42:59 +01:00
Ricki Hirner
eb8db47cea Simplify TasksAppManager and TasksAppWatcher (#1193)
* Simplify TasksAppManager and TasksAppWatcher

* AutomaticSyncManager: renable setSyncInterval to enable

* TasksAppWatcher: actually select provider
2024-12-24 13:32:30 +01:00
Ricki Hirner
7384feeafb Update dependencies 2024-12-24 10:01:11 +01:00
Ricki Hirner
d10add8367 Fetch translations from Transifex 2024-12-23 14:14:05 +01:00
Ricki Hirner
51bd163069 Version bump to 4.4.5 2024-12-23 14:12:01 +01:00
Ricki Hirner
90280066ee Version bump to 4.4.5-beta.2 2024-12-21 12:55:20 +01:00
Ricki Hirner
03a52e96ad Address book accounts: bind to accounts (again) (#1184)
* [WIP] Scope address book acounts to accounts again

* [WIP] Tests

* Fix LocalAddressBookStoreTest

* Adapt AccountsCleanupWorker

* Migration to assign accounts to address books (again)

* Change account in address books on account rename
2024-12-21 12:53:53 +01:00
Ricki Hirner
5890b3cc5e AccountSettings: one class per migration, tests (#1181)
* [WIP] new AccountSettingsMigrations + tests for v17

* [WIP] Tests

* Finish tests for v17

* Move migrations to separate classes

* Improve test

* KDoc
2024-12-20 11:45:13 +01:00
Ricki Hirner
a02bc56b44 Fetch translations from Transifex 2024-12-17 10:37:17 +01:00
Ricki Hirner
4939c9fc4d Version bump to 4.4.5 2024-12-17 10:19:21 +01:00
Ricki Hirner
c2524b085e LocalAddressBookStore: return all address books, including orphaned ones (#1168)
* LocalAddressBookStore: return all address books, including orphaned ones

* Add test

* Update tests
2024-12-17 10:18:46 +01:00
Arnau Mora
d892dd2b9c Fixed padding problems with Edge-To-Edge (#1171)
* Added navigation bars paddign

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Only consuming top padding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-12-15 20:01:31 +01:00
Ricki Hirner
95ebce5722 Update Compose 2024-12-12 17:11:22 +01:00
Arnau Mora
4b2f032a57 [Push] Update specification compliance (#1151)
* Update dav4jvm, agp, kotlin and ksp

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated topic extraction

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated push-message definition

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Newer dav4jvm

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-12-12 17:08:29 +01:00
Ricki Hirner
bc596edfb3 Version bump to 4.4.5-alpha.1 2024-12-07 13:55:02 +01:00
Ricki Hirner
e18534ab9f Use AutomaticSyncManager to manage periodic workers and sync framework (#1157)
* Introduce AutomaticSyncManager

* AccountRepository: TasksAppManager: use AutomaticSyncManager

* Fix AccountSettings.account visibility
2024-12-07 13:52:52 +01:00
Ricki Hirner
9ae03dbc6f AccountSettings: make enable flag for sync intervals explicit (#1156) 2024-12-07 12:41:53 +01:00
Ricki Hirner
042dd3fba2 [DI] Replace injection of Application by Context with ApplicationContext annotation 2024-12-07 12:00:57 +01:00
Ricki Hirner
5d6959c47e Pass sync errors back to sync framework (bitfireAT/davx5#632)
* [WIP] Adapt SyncResult

* Remove numeric stats (not well-defined and not really used)

* Pass "too many retries" or "database error" back to sync framework on hard errors
2024-12-07 11:45:04 +01:00
Arnau Mora
239038ab77 Get rid of Head response cache (#1155)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-12-06 11:36:24 +01:00
Ricki Hirner
7097bf9523 Move BaseSyncWorker.exists to SyncWorkerManager 2024-12-05 11:24:19 +01:00
Ricki Hirner
53bc5a6641 Update dependencies, AGP, gradle 2024-12-04 11:49:36 +01:00
Arnau Mora
9e060f6651 [Push] Allow UP distributor selection (#1146)
* Added UP distributor selector

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Load distributor selection from UnifiedPush

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved logic to model

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added UP distributor selector

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Load distributor selection from UnifiedPush

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved logic to model

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated usages

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of stateIn

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Code cleanup and auto-selection

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Cleanup

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Code cleanup

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added push distributor disabling

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added push distributor selection dialog preview

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Update strings

* Minor changes

* Got rid of push endpoint

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of automatic push distributor selection

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Typo

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved init to the bottom

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* MutableStateFlow: use setValue instead of emit

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-12-04 11:38:49 +01:00
Ricki Hirner
cc8fc4734f Move SyncUtils to SyncWorkerManager 2024-12-02 21:57:20 +01:00
Ricki Hirner
0733fef213 AppSettingsModel: clearly identify task app-related entries 2024-11-30 11:34:40 +01:00
Ricki Hirner
f977cc01eb Update dependencies (including ical4android) 2024-11-30 11:20:56 +01:00
Arnau Mora
30dc2cb221 Use specific help URL from current login type (originally in bitfireAT/davx5#611)
---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-11-29 16:48:26 +01:00
Ricki Hirner
fc6b605693 Remove unused drawables 2024-11-29 16:23:56 +01:00
Sunik Kupfer
4f1176fd99 Don't show app intro again after recomposition (#1142)
* Remember whether we showed the app intro already

* Add comment
2024-11-27 22:53:54 +01:00
Sunik Kupfer
4ff7ff8746 Extract sync framework interaction to separate class (#1137)
* Add SyncFrameworkIntegration class

* Use SyncFrameworkIntegration class

* Update kdoc

* Fix test

* Fix isSyncable check

* Update kdoc

* Remove singleton requirement

* Extract anonymous method setting the content trigger
2024-11-25 15:33:25 +01:00
Ricki Hirner
2f26c6c365 Fetch translations from Transifex 2024-11-24 14:53:37 +01:00
Ricki Hirner
d8bff41bc4 Version bump to 4.4.4 2024-11-24 14:51:32 +01:00
Ricki Hirner
878e2bb3ad Fetch translations from Trasifex 2024-11-22 17:16:19 +01:00
Ricki Hirner
dc1c72cdd3 Version bump to 4.4.4-rc.1 2024-11-22 17:14:36 +01:00
Arnau Mora
1dc7f3de64 Use timezone ID instead of full VTIMEZONE in DB (#1104)
* Migrated to using timezone id directly

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* added tests for collection timezone

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed calendar definition

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added missing line break

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Unnecessary forced null

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added ksp.incremental

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Version is now 16

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Comments cleanup

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added automatic migrations tests

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Renamed block to assertionsBlock

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added comment

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of extra comments

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of PR url

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added column drop with try-catch

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added `SdkSuppress`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added version check for column drop

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Included SDK 34

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Commit recover

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* DB tests: inject Context

* DB migration tests: validate in target schema version; manual migration definitions: use new Kotlin syntax

* Use auto-migration instead of manual migration.

- No manual DROP COLUMN required.
- Added support for migration of unparseable VTIMEZONE.

* Update ical4android

* Service detection: ask for calendar default timezone (+ ID)

* Remove dropColumn because we don't need it for the current migration.

Leave the v4 -> v5 migration as it is.

* KDoc

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-11-22 17:13:57 +01:00
Ricki Hirner
d20c613044 Update dependencies, including ical4android (closes bitfireAT/davx5#625) 2024-11-22 16:56:13 +01:00
Ricki Hirner
fe8eabce1b Show sync error notification even with big local resource (#1139)
Sync error notification: truncate local info
2024-11-22 00:46:48 +01:00
Ricki Hirner
b5790bfd09 Update dependencies and gradle wrapper 2024-11-20 16:11:40 +01:00
Arnau Mora
9e7de1c8ca Enforced edge-to-edge (#1078)
* Enforced edge-to-edge

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed bottom padding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Recovered ime padding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated theme colors for intro

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Always using light primary color in intro

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Simplified settings

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added disabling of top padding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Improved syntax

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed padding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Add comments

* Use default fallback colors for status/system bar instead of transparent

- With transparent, system bar buttons are white on very-light-grey

* Use safeContentPadding for intro

* Use scrim color from theme

* IntroPage: correctly consume insets; Assistant: KDoc/example

* Branding box extends to upper edge

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Set correct navigation bar color, if applicable

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Let content shine through navigation bar / especially for lists like settings

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Brand background is no longer padded

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Window insets are now consumed by AppTheme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed padding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Do not consume insets

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Move content

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Enabled E2E in intro

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-11-20 13:05:09 +01:00
Ricki Hirner
15d2072f16 Version bump to 4.4.4-alpha.1 2024-11-19 19:55:41 +01:00
Ricki Hirner
0f4e48ad4d Move local collection management from companion objects to LocalDataStore (#1125)
* [WIP] Move local collection management from companion objects to LocalDataStore

* Move Syncer.getLocalCollections to LocalDataStore.getAll()

- note that things like the "sync_enabled" Android calendar flag are not supported and always set to true

* Minor changes

* Implement LocalJtxCollectionStore

* Implement LocalTaskListStore

* Fix tests

* Drop initialUserData

* Address book read-only applies to entries of address book itself, so moving to LocalAddressBook

* KDoc, shouldBeReadOnly

* Test accountName

* Remove obsolete address book factory

* Test create address book

* Test createAccount

---------

Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2024-11-18 12:46:14 +01:00
Ricki Hirner
41075e442c Push: fix expiration XML element in push-register message 2024-11-17 14:47:41 +01:00
Ricki Hirner
3a16b5ca3f [Push] Handle subscription expiration (#1131)
* Store subscription expiration in DB

* Regularly run PushRegistrationWorker, if needed

* Skip re-registering subscriptions that are not about to expire

* Add back-off for PushRegistrationWorkerManager

* Request expiration in 3 days

* Show expiration in UI, timestamps in seconds

* Fix tests
2024-11-17 14:39:22 +01:00
Sunik Kupfer
32925dc18b Set or update read-only flag when address books are renamed (#1124)
* Always evaluate read only

* Extract read only evaluation to companion method

* Extract read only evaluation to companion method

* Add test

* Always pass forceReadOnly flag

* Minor KDoc changes

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-11-07 11:45:27 +01:00
Sunik Kupfer
cf15dd3e0e Set contacts provider settings when address books are renamed or update (#1123)
Set contacts provider settings when address books are renamed and updated
2024-11-06 22:10:44 +01:00
Ricki Hirner
3317a8d355 Move dirty verifier logic to ContactDirtyVerifier (#1122)
* Move dirty verifier logic to ContactDirtyVerifier

* KDoc

* KDoc, fix contactDataHashCode

* Fix tests

* Add KDoc
2024-11-06 12:05:09 +01:00
Ricki Hirner
154d1e6bc8 AccountSettingsMigrations: fix comment, require tests (#1116)
AccountSettingsMigration: fix comment, require tests
2024-11-01 15:59:14 +01:00
Ricki Hirner
1a19d5cd17 Update AGP 2024-11-01 11:38:58 +01:00
Ricki Hirner
b721e83377 Update dependencies 2024-10-31 09:55:19 +01:00
Ricki Hirner
f69533b049 Remove obsolete permissions for SDK level 22 (min SDK = 24) 2024-10-28 11:51:59 +01:00
Sunik Kupfer
d00353ba9c Replace android sync framework result class with our own (#1094)
* Use our own SyncResult data class

* Minor comment changes

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-10-25 14:13:43 +02:00
Ricki Hirner
dc0d4f371a More compatible processing of multiget responses (#1099)
* Ignore multi-get responses without calendar/contact data

* Add comment
2024-10-25 12:44:54 +02:00
Arnau Mora
3d198f5454 LocalAddressBook: rename account to addressbookAccount (#1095)
* Upgraded vcard4android

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Replaced all usages of addressBookAccount

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Minor changes

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-10-22 14:07:53 +02:00
Ricki Hirner
1802740a2d Version bump to 4.4.3.2 2024-10-20 16:32:12 +02:00
Ricki Hirner
138e517d23 LocalAddressBook: move contacts when renaming the address book account (#1084)
* LocalAddressBook: move contacts when renaming the address book account

* Don't make contacts dirty when moving

* Move isDirty to tests because it's only required for tests

* We don't have to set the user-data twice

* Add test for groups
2024-10-20 16:31:31 +02:00
Ricki Hirner
166b2ac220 Bump version to 4.4.3.1 2024-10-18 16:53:28 +02:00
Ricki Hirner
450a418994 Don't crash when logging null parameter (#1081) 2024-10-18 16:51:26 +02:00
Ricki Hirner
d4e9e2a8f7 Reduce warnings, lint 2024-10-17 16:40:09 +02:00
Ricki Hirner
ecc59dda99 Update dependencies 2024-10-17 16:32:35 +02:00
Ricki Hirner
9c2afbab09 Fetch translations from Transifex 2024-10-17 16:14:04 +02:00
Ricki Hirner
cebf2d9dfd Version bump to 4.4.3 2024-10-17 16:10:18 +02:00
Sunik Kupfer
5f49c675c8 Try to adhere to google play guidelines for background location permission (bitfireAT/davx5#614)
* Always show "WiFi SSID card" in account settings when SSID restriction is active and adapt content according to whether all conditions are met or not

* Move explanation to top and add paragraph

* Remove unnecessary parenthesis

* Use two text composables and no spacer

* Fix preview
2024-10-16 11:11:44 +02:00
Ricki Hirner
62c46e123d Avoid very long log lines and resulting OOM (#1073)
* PlainTextFormatter: truncate log lines to ~10000 characters

* Update vcard4android which doesn't dump Contact photos anymore

* Add truncation test
2024-10-15 15:22:03 +02:00
Ricki Hirner
5f1215801d Update dependencies 2024-10-15 12:32:45 +02:00
Ricki Hirner
930977c44b Make collections in CollectionsList clickable again (#1075)
CollectionsList: use modifier again
2024-10-15 11:40:09 +02:00
Arnau Mora
a0d152a66f Fixed surface container color in dark theme (#1069)
* Fixed surface color in dark theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Account screen: higher contrast for collection cards

* Account screen: use normal instead of elevated cards

* Adapt colors of card lists

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-10-14 15:03:08 +02:00
Ricki Hirner
a8883427bc Fetch translations from Transifex 2024-10-10 18:25:36 +02:00
Ricki Hirner
7a8dbef80b Version bump to 4.4.3-rc.1 2024-10-10 18:23:05 +02:00
Ricki Hirner
4a40bb3d6f Syncer: make sure collections which are deleted are not synced (#1065)
* Syncer: make sure collections which are deleted are not synced

* Syncer: log when local collection is removed

* Update KDoc and tests

* Handle all CRUD work in updateCollections

* Update naming, KDoc, tests

* Minor changes (KDoc, naming)
2024-10-10 18:08:13 +02:00
Arnau Mora
c805e549ff [Push] Show notification on push notification (until sync is started) (#1043)
* Added sync pending notification

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved notify function to PushNotificationManager

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added ongoing and only-alert-once

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added notification hiding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of `cancel`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed comments

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added content intent and sub text

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated usages

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Review changes

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-10-10 15:50:53 +02:00
Sunik Kupfer
26a670c181 Fix old address book accounts not being deleted (#1039)
* Log warning instead of throwing exception when not possible to find account for address book account

* Run sync and accounts cleanup in migration

* Rename accounts in migration

* Run account settings migrations on background thread

* Revert "Run account settings migrations on background thread"

This reverts commit 6b578da4f1.

* Add tests for AccountsCleanupWorker

* Move companion object to end of class

* Don't use AccountRepository for address book accounts

* Update account user data in LocalAddressBook

* Minor changes (naming etc)

* Add log line when migrating

* Try to fix test error

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-10-09 13:28:54 +02:00
Arnau Mora
5b54c9dff0 MKCALENDAR: send VTIMEZONE in calendar-timezone (#1044)
* Upgrade dav4jvm

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* separate CalendarTimezone and CalendarTimezoneId

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed timezone name setting

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed `VTIMEZONE` conversion

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using text instead of CDATA

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed spec

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added comment

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Renamed `timezoneDef` to `timezoneId`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Upgrade dav4jvm

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* separate CalendarTimezone and CalendarTimezoneId

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed timezone name setting

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed `VTIMEZONE` conversion

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using text instead of CDATA

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed spec

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added comment

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Renamed `timezoneDef` to `timezoneId`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* [CI] Update workflows to Java 21

* Set default value of the timezone state to null

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-10-09 00:12:34 +02:00
Ricki Hirner
1ca73b67a4 Add KDoc, rename AccountUtils to SystemAccountUtils (#1059) 2024-10-07 18:52:18 +02:00
Ricki Hirner
194c587476 Update Compose, Kotlin, gradle 2024-10-07 12:38:18 +02:00
Sunik Kupfer
1193027e5f Add account name to address book account name (#1050)
* Add account name to address book account name

* Add parenthesis for account name and a hashtag for the collection id

* Use the correct id

* Move DI entry point to where it is used
2024-10-07 12:26:53 +02:00
Ricki Hirner
7de7980860 Use SafeAndroidUriHandler to prevent crashes when no browser is installed (#1058)
* Use SafeAndroidUriHandler in AppTheme

* UiUtils: use DI for Logger

* SyncWorkerManager: use DI for Logger
2024-10-07 10:44:36 +02:00
Ricki Hirner
fc7f42c6fa Sync worker management: move logic out of companion object (#1056)
* Sync worker management: move logic from companion object to new class

* Fix tests

* Move re-sync inputs from [OneTimeSyncWorker] to [BaseSyncWorker] as they're processed there

* Remove useless Companion
2024-10-07 09:34:02 +02:00
Sunik Kupfer
196bfb3aea Don't use AccountSettings on main thread (#1049)
* Document that AccountSettings shouldn't be used in the main thread

* Throw exception when AccountSettings are used on the main thread

* Don't access AccountSettings on main thread

* Don't access AccountSettings on main thread

* Don't access AccountSettings on main thread
2024-10-03 15:36:42 +02:00
Sunik Kupfer
cb5798833d Ignore lint AppLinkUrlError (#1053) 2024-10-03 15:33:39 +02:00
Ricki Hirner
a1148613e9 [CI] Update workflows to Java 21 2024-10-02 13:08:11 +02:00
Ricki Hirner
12529fa9bd Update toolchain and AGP 2024-10-02 12:54:50 +02:00
Ricki Hirner
d743d19a3d AccountsScreen: better preview 2024-09-19 15:42:45 +02:00
Ricki Hirner
4dcee27e22 Version bump to 4.4.3-beta.1 2024-09-19 15:17:20 +02:00
Sunik Kupfer
b6ceaa7efc Remove concept of main accounts (#989)
* Acquire account settings via address book account

* Extract the code to find an address books main account to the account repository

* Use collection id as reference in address book account

* Remove obsolete baos

* Find main account directly from collection in SyncManager

* Require main account to get account settings

* Stop deleting address book accounts without a main account, since they may exist on their own now

* Require content provider and introduce static deleteByCollection method

* Update KDoc

* Show all address book accounts separately

* Drop mainAccount method

* [DI] Use AssistedInject for LocalAddressBook

* Renaming, remove "main account" concept

* Fix debug info

* AccountsCleanupWorker: Rename main account to account

* Further remove main accounts

* Reduce redundancy

* AccountSettings: check account type

* AccountSettingsMigrations: drop v5 -> v6 migration (not tested anyway)

* AccountRepository: directly delete accounts

* Remove obsolete workerAccount

* Get all address books, even if not sync enabled

* Delete orphan address book accounts

* Rename two more occurrences of main account concept

* AccountSettings: allow test accounts

* Syncer: rename methods for clarity, add KDoc

* Drop empty test class

* Make code more readable and add comment

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-09-19 15:06:45 +02:00
Ricki Hirner
5c6f712d32 Update AGP and dependencies 2024-09-19 14:02:34 +02:00
Arnau Mora
5180b99af2 Moved pull-to-refresh indicator below tabs (#1028)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-09-16 15:48:48 +02:00
Arnau Mora
dcb7e315b9 ClickableText for URLs has been deprecated (#1024)
* Got rid of `UrlAnnotation`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Deprecated and suggested a ReplaceWith

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Optimized imports

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Replaced usages of `ClickableTextWithLink`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed `ClickableTextWithLink`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Migrated `ClickableTextWithLink`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Remove experimental text api annotations

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2024-09-16 15:02:17 +02:00
Arnau Mora
111481cd00 Added isLoading to Assistant (#1027)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-09-16 14:35:36 +02:00
Ricki Hirner
4dc7df7c53 Reword login text (#1026)
- Change empty accounts text
- Add privacy note to Login screen
- Changed last button text
2024-09-16 14:34:08 +02:00
Arnau Mora
cf609288e1 Update Dependencies (#1017)
* Upgrade dependencies

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Upgrade dependencies

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Migrated pull to refresh

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Migrated `LocalMinimumInteractiveComponentEnforcement`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed disabling of linting

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Optimize imports

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Increased indicator show time

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-09-14 13:22:43 +02:00
Ricki Hirner
0b9d4cd3b3 Unsubscribe push from unsynced collections (#1011)
* Unsubscribe push from unsynced collections

* Remove subscription from DB, too

* Subscription: catch HTTP errors
2024-09-10 12:07:04 +02:00
Arnau Mora
0581417bba Increase SDK level to 35 (#1003)
* Increase SDK level

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed nullability issues

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Increase SDK level

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed nullability issues

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Replaced `removeFirst()` by `removeAt(0)`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Switched to null check instead of NPE catch

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using orEmpty

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-09-10 11:18:40 +02:00
Ricki Hirner
f8fb016a27 [CI] Better test names 2024-09-06 11:29:15 +02:00
Ricki Hirner
8c3d1cdeae InitCalendarProviderRule: make multiple attempts to create a calendar (#1007) 2024-09-06 11:18:08 +02:00
Arnau Mora
4a4dc24cdf dark theme / black text basically unreadable on dark background (#986)
* Provided content color

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Adjusted bar color

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of theme changes

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added wrapping scaffold

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed colors

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-09-06 10:07:30 +02:00
Arnau Mora
49a51ef384 Reproducible Builds (for IzzyOnDroid) (#995)
* Added `BUILD_DATE` environment variable

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Excluded `generated`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed argument

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using build time from git

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed unused import

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Got rid of build date

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of build date

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2024-09-06 09:48:29 +02:00
Arnau Mora
fc698040aa lint fails in AboutActivity (#1001)
* Disabled `CoroutineCreationDuringComposition` in lint

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved disable

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-09-04 17:46:44 +02:00
Ricki Hirner
6cbd71ab50 Update dependencies (including AGP) 2024-09-04 11:03:29 +02:00
Ricki Hirner
47f078dcd7 Version bump to 4.4.3-alpha.1 2024-08-21 12:23:54 +02:00
Ricki Hirner
be6ab8728c Update dependencies and Gradle 2024-08-21 12:18:51 +02:00
Ricki Hirner
2908bba298 [CI] Compile job: only compile sources, not tests 2024-08-21 12:01:22 +02:00
Sunik Kupfer
b962b68631 Use standard content provider instead of TaskProvider in TaskSyncer (#982)
* Update ical4android

* Use standard content provider in TaskSyncer

* Check version instead of acquiring TaskProvider

* Add sync result error

---------

Co-authored-by: Arnau Mora <arnyminerz@proton.me>
2024-08-20 10:47:50 +02:00
Sunik Kupfer
fca7c09105 Tests for sync algorithm (#974)
* Prepare Syncer for tests

* Test sync honors preparation result

* Refactor sync algorithm into smaller testable methods

* Write weak tests for the individual methods

* Update KDoc

* Minor changes
 - update comments
 - update test method spacing
 - replace empty array method

---------

Co-authored-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-08-19 13:42:22 +02:00
Sunik Kupfer
60c6aba2d2 Log stop reason when sync worker is cancelled (#984) 2024-08-17 16:06:21 +02:00
Ricki Hirner
70f6f2603e SyncAdapterServices: Use a coroutine scope to cancel waiting on framework request (#977)
* SyncAdapterServices: Use a coroutine scope to cancel waiting on framework request

* Added tests
2024-08-14 14:00:54 +02:00
Ricki Hirner
5d4c9c8d94 Don't overwrite calendar/task list color with default color (#971)
Calendars, task lists: always set color at creation, then overwrite only when available from server
2024-08-14 10:13:08 +02:00
Ricki Hirner
4378bee042 [CI] Skip compile job when not on main branch (#978) 2024-08-13 13:19:17 +02:00
Ricki Hirner
3776b50bbc Update dependencies, Kotlin, AGP 2024-08-12 14:19:00 +02:00
Sunik Kupfer
a9c7e1929f Fix sync not running directly after enabling a collection (#966)
Retrieve local sync collections only once and return them on creation
2024-08-12 11:55:35 +02:00
Ricki Hirner
318b9be77e AccountRepository: don't add onAccountsUpdated listener on main thread 2024-08-08 11:30:56 +02:00
Ricki Hirner
26cb845950 Reduce StrictMode annoyance 2024-08-08 10:40:25 +02:00
Sunik Kupfer
eae6d0c578 Fix coincidence naming of LocalCollection members (#957)
* Fix overlapping method name and use interface everywhere

* Fix overlapping property name

* Update logger usage

---------

Co-authored-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-08-08 10:18:21 +02:00
Ricki Hirner
6df0925e50 Version bump to 4.4.2 2024-08-06 13:31:14 +02:00
Ricki Hirner
f7ee1ea931 [UI] Show push support status in collection details (#961)
* Collection details: show WebPush support

* Collection view: show Push subscription time
2024-08-06 13:29:31 +02:00
Ricki Hirner
16731d3a5a Suppress dnsjava warning because of missing Context (#959) 2024-08-06 12:39:57 +02:00
Ricki Hirner
54e09acca3 Don't generate/let Gradle user cache grow in main branch 2024-08-06 11:26:46 +02:00
Ricki Hirner
46698a76b5 Fetch translations from Transifex 2024-08-05 17:41:06 +02:00
Ricki Hirner
e26a8519ff Version bump to 4.4.2-rc.1 2024-08-05 17:40:06 +02:00
Arnau Mora
410c70a47d Passing collection directly (#927)
* Passing `collection` directly

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed imports

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fix tests

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Log sync time using repository; no need for account/service check anymore

* SyncManagerTest: don't write SyncStats to DB

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-08-05 17:24:46 +02:00
Sunik Kupfer
bf1bdfc8ab Generalize syncer (#907)
* Use address book contacts content provider provided by syncer

* Close any acquired content provider after sync

* Acquire/Check task provider provider as preparation step

* Provide sync arguments at syncer creation

* Acquire ContentProviderClient in syncer implementations

* Generalize sync algorithm in Syncer

* Use contacts authority for address books

* Generalize sync algorithm in CalendarSyncer

* Generalize sync algorithm in SyncerTest

* Generalize sync algorithm in TaskSyncer

* Rename preparation method and add an after sync method

* Generalize sync algorithm in JtxSyncer

* Generalize sync algorithm in AddressBookSyncer

* Use repositories instead of DAOs

* Replace deprecated log statements

* Use generic type for collection types

* Infer authorities when possible and pass only task authorities along

* No need to close TaskProvider explicitly

* Use colors only where needed

* Use provider with auto closable

* Get sync collections in syncer implementations

* Delete syncer test

* Pass provider through methods instead of using lateinit property

* Reorder constructor arguments

* Remove trailing commas

* Remove obsolete undocumented conditional

* Reorder methods

* Reorder methods

* Abort sync when preparations fail

* Drop obsolete permission check

* Use generics for url and delete

* Use generic for update

* Use generic for syncCollection

* Rename create to createCollection for consistency

* Revert "Rename create to createCollection for consistency"

This reverts commit 0fee4fe7fcf3ec8ef965c9a2e0db991a1bbbbcf7.

* Revert "Use generic for syncCollection"

This reverts commit ae129fd17e06146a1e9f8631e3cdbd2dc0a4db06.

* Revert "Use generic for update"

This reverts commit 42dd665851ba83a75bb498b98bd56624e2b09647.

* Revert "Use generics for url and delete"

This reverts commit 7ae1425039656d4a9937628ca1799ce8c59ceebb.

* Move delete() to LocalCollection

* Move url to LocalCollection

* Fix local test collection

* Minor changes

* Minor changes
- make sync private
- use to autoclose provider

* Query for sync collections once only

* Add KDoc and update comments

* Make property a local variable

* Update KDoc

* Add back ose conditional

* Remove blank line at beginning of method

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-08-05 17:22:07 +02:00
Arnau Mora
5cbbfb39aa Update dav4jvm for better logging (#956)
* Upgrade dav4jvm

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using `XmlReader`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-08-05 16:42:24 +02:00
Ricki Hirner
62d5a21d05 Update ical4android, specify ical4j log level (#946)
* Update ical4android, specify ical4j log level (was previously in ical4android)

* Add "dependencies" label for auto-generated release notes
2024-07-28 16:54:01 +02:00
Ricki Hirner
2a7cf1ae17 Fix R8 rules 2024-07-28 14:34:55 +02:00
Ricki Hirner
1062eaa58a Version bump to 4.4.2-beta.1 2024-07-28 13:54:03 +02:00
Ricki Hirner
69bde87589 WebDAV mounts: fix quota view (#945)
* Refresh quota for newly created mounts

* WebDAV mounts screen: take quota from root document
2024-07-28 13:53:39 +02:00
Ricki Hirner
4111fe08d2 Drop userAgent build configuration variable 2024-07-28 12:36:59 +02:00
Ricki Hirner
19fb969040 Drop userAgent build configuration variable 2024-07-28 11:20:25 +02:00
Ricki Hirner
74a22cd24d Update dependencies 2024-07-28 11:14:53 +02:00
Ricki Hirner
4e264076d1 Amend PR template 2024-07-26 14:50:21 +02:00
Ricki Hirner
c3fe1b04e5 Backport minor changes from non-ose (#941)
Login: correctly combine input flows for account details UI state (backport bitfireAT/davx5#575)
2024-07-26 12:04:19 +02:00
Ricki Hirner
342314363b Simplify InitCalendarProviderRule (#940)
Simplify InitCalendarProviderRule
2024-07-25 22:56:07 +02:00
Ricki Hirner
d0358a9980 Use test account type (without sync adapters) for instrumented tests (bitfireAT/davx5#601)
* Use test account type (without sync adapters as side effects) for instrumented tests

* Provide standardized way to get test account; re-enable LocalAddressBookTest
2024-07-25 20:57:23 +02:00
Ricki Hirner
fbcf6996ad Don't access DB in UnifiedPushReceiver main thread 2024-07-24 17:43:58 +02:00
Ricki Hirner
cf994ee82e Move UrlUtils methods to DavUtils 2024-07-24 17:15:57 +02:00
Ricki Hirner
9880dd5158 Move TaskUtils to TasksAppManager (closes #937) 2024-07-24 16:39:30 +02:00
Ricki Hirner
8e7d289971 Tests: don't create account as long as Hilt is not ready (#939) 2024-07-24 15:13:34 +02:00
Ricki Hirner
d4c05b9282 NotificationRegistry: enforce creation of channels before they can be accessed 2024-07-24 15:13:06 +02:00
Ricki Hirner
768f462549 Use AccountRepository to get list of accounts (#938)
* Use AccountRepository to get list of accounts

* No AndroidEntryPoint in (Glance) widget
2024-07-24 14:50:48 +02:00
Ricki Hirner
cbd9a55c15 Replace some non-injected loggers 2024-07-24 09:53:31 +02:00
dependabot[bot]
4e496265e4 Bump dnsjava:dnsjava from 3.5.3 to 3.6.0 (#933)
Bumps [dnsjava:dnsjava](https://github.com/dnsjava/dnsjava) from 3.5.3 to 3.6.0.
- [Release notes](https://github.com/dnsjava/dnsjava/releases)
- [Changelog](https://github.com/dnsjava/dnsjava/blob/master/Changelog)
- [Commits](https://github.com/dnsjava/dnsjava/compare/v3.5.3...v3.6.0)

---
updated-dependencies:
- dependency-name: dnsjava:dnsjava
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 22:08:16 +02:00
Arnau Mora
73d0b63705 Debug info: Sync interval "0 min" when actually -1 (manual) (#928)
Added check for only manual sync

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-07-23 19:08:06 +02:00
Ricki Hirner
a361888d94 Version bump to 4.4.2-alpha.2 2024-07-23 01:11:39 +02:00
Ricki Hirner
907b38fd6a Better verbose log formatting 2024-07-22 00:18:52 +02:00
Ricki Hirner
bf0e169cf1 Remove obsolete Logger object (#930) 2024-07-21 23:32:45 +02:00
Ricki Hirner
fd2b3f0018 Refactor SettingsManager/SettingsProvider to use DI properly (bitfireAT/davx5#599)
* Refactor SettingsManager/SettingsProvider to use DI

* Fix tests

* Remove BaseDefaultsProvider

* Fix gplay tests
2024-07-20 21:47:15 +02:00
Ricki Hirner
6217582677 Debug builds: add StrictMode for ThreadPolicy, don't setup crash handler 2024-07-20 18:51:57 +02:00
Ricki Hirner
d03dc1f37d Remove unnecessary SyncComponent 2024-07-20 18:40:42 +02:00
Ricki Hirner
d33e4dcb23 Update dependencies 2024-07-20 18:35:31 +02:00
Ricki Hirner
f4c02d4ab6 [DI] Use @Inject lateinit var for abstract classes (#929)
* Use @Inject lateinit var for abstract classes (subclasses have @Inject constructor)

* Fix tests
2024-07-20 18:19:04 +02:00
Arnau Mora
fafa358dd8 DB: some missing indices (#890)
* Added index for `ownerId`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Increased version number

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Increased version number

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added version

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added pushTopic as index

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved index

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed uniqueness

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed indexing method

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved `parentId` to independent index

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-07-20 17:35:39 +02:00
Ricki Hirner
d5d6592ae2 SyncWorker: use Provider for lazy injection (#925)
* SyncWorker: use Provider for lazy injection

* Use Provider to lazyily inject SyncAdapter
2024-07-19 18:09:20 +02:00
Ricki Hirner
ac48e65b1a Remove deprecated code / annotate as deprecated 2024-07-19 17:41:47 +02:00
Ricki Hirner
26f95db62a Re-factor Notifications and various Utils to DI (#924)
* [WIP] Use NotificationRegistry to post notifications

* Replace NotificationUtils by NotificationRegistry

* Re-factor SyncConditions; move tests to default location

* Describe notification channels
2024-07-19 17:10:29 +02:00
Ricki Hirner
5c4d697767 WebDAV cache: re-factor using Guava and Hilt (#921)
* Use per-resource Guava cache with weak keys and values as WebDAV page cache

* Replace ExtendedLruCache by Guava cache

* Use injected logger in DavDocumentsProvider

* Use DI for RandomAccessCallback, move Wrapper to outer class

* Fix tests

* Use Hilt for more classes

* Use Guava LoadingCache as page cache

* Fix tests

* Minor code change
2024-07-19 11:59:10 +02:00
Ricki Hirner
59a57fc40a Update dnsjava, use desugaring with nio, refactor DNS resolving (#917)
* Update dnsjava, use desugaring with nio, refactor DNS resolving

* KDoc

* Rewrite bestSRVRecord test

* Add pathsFromTXTRecords tests

* Add desugaring note

* Rename @AssistedFactory method to "create"

* testBestSRVRecord_MultipleRecords_Priority_Same: broaden range for distribution
2024-07-18 11:09:00 +02:00
Ricki Hirner
50c13e5b6d Install uncaught exception handler in a separate startup plugin (bitfireAT/davx5#597) 2024-07-18 10:21:31 +02:00
Ricki Hirner
3a38a06302 Version bump to 4.4.2-alpha.2 2024-07-17 12:41:50 +02:00
Ricki Hirner
c489002f5c Create interface for startup plugins (#915)
* Create StartupPlugin interface, implement with TasksAppWatcher (don't run during tests)

* KDoc
2024-07-16 14:45:40 +02:00
Ricki Hirner
7cbc9bd4f5 Sync: take URL from DB (Collection) instead of making a detour over other places 2024-07-16 12:14:10 +02:00
Ricki Hirner
1fd65a4d42 Use DI for AccountSettings and Android tests (#911)
* Use DI for AccountSettings

* AccountSettings: clarify different account variables

* Fix tests

* Inject @ApplicationContext instead of Application

* Add Hilt rule to various tests

* Fix tests

* Consequently use injected @ApplicationContext in Android tests; fix Hilt rules
2024-07-15 19:39:21 +02:00
Sunik Kupfer
b3cc24e4be Fix: Syncer synchronizes only once (#910)
* Use a separate HashMap to determine new remote collections

* Close provider after sync
2024-07-15 11:00:34 +02:00
Ricki Hirner
556741ae1e Get rid of Apache Commons (#901)
* Replace StringUtils

* Replace DateFormatUtils and NumberUtils

* Fix tests

* Replace ReflectionToStringBuilder

* Rewrite MemoryCookieStore to get rid of MultiMap

* Get rid of org.apache.commons.lang3.exception.ExceptionUtils

* Replace org.apache.commons.collections4.CollectionUtils

* Replace DigestUtils

* Replace ContextedException by SyncException

* Fix HttpClientTest

* SyncManager: remove obsolete code

* Fix StringUtilsTest

* DiskCache: add hit/miss logging

* DebugInfoModel: simplify PrintWriter generation
2024-07-14 10:22:00 +02:00
Ricki Hirner
b1bcf32535 Update AGP 2024-07-13 18:44:53 +02:00
Ricki Hirner
be43d360ba Refactor logging (#906)
* LogcatHandler: log source class as tag and not in the text, remove max length

* Separate LogManager and Logger usage

* Improve StringHandler for DavResourceFinder

* Tests, KDoc
2024-07-13 17:37:07 +02:00
Ricki Hirner
0adceb64ec Squashed commit of the following:
commit 60219aafc7
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Thu Jul 11 23:59:54 2024 +0200

    Version bump to 4.4.1.1

commit 209fdf3e7c
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Thu Jul 11 23:37:37 2024 +0200

    AccountSettings v16: re-enqueue periodic sync workers with correct class name (bitfireAT/davx5#593)
2024-07-12 12:30:19 +02:00
Ricki Hirner
5ebef30abb Tasks screen: adapt padding and recommendation (#898)
Tasks screen: adapt padding and text
2024-07-11 15:47:56 +02:00
Sunik Kupfer
3d65afbf8f Provide collection to SyncManager (#881)
* Provide collection to CalendarSyncManager

* Provide collection to ContactsSyncManager

* Provide collection to JtxSyncManager

* Provide collection to TasksSyncManager

* Provide collection to SyncManager

* Fix test

* Minor changes

* Add kdoc

* Minor changes

- added KDoc
- SyncManager can never handle address book authority (content
  authority for contacts is contacts authority)
- moved @UsesThreadContextClassLoader to respective sync manager

* Mock collection in test

* Add comments separating the sync process into separate steps

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-07-11 14:09:35 +02:00
Sunik Kupfer
51f01b215c Remove address books sync authority and content provider (#877)
* Drop address book provider

* Drop address book sync adapter service

* Replace address books authority with contacts authority

* Update kdoc

* Revert "Update kdoc"

This reverts commit dfb14d466f6c58d9422e59c770e0a5348a497b7d.

* Revert "Replace address books authority with contacts authority"

This reverts commit 0e15bf11b3235dfbc38696741100210fbe497dbd.

* Don't enable addressbook sync for main account

* Acquire content provider in Syncer

* Use contacts authority instead of address book authority when acquiring content provider

* Set default sync interval for address book authority again

* Minor re-ordering of lines

* Add comment, rename variable

* Move SyncAdapterServices out of adapter package

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-07-11 11:17:09 +02:00
Arnau Mora
c02bf942e4 Replaced "Create" by "Add" when creating accounts (#892)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2024-07-08 11:11:21 +02:00
Ricki Hirner
d7025d4e9e Use proper DI for Syncer implementations (#889) 2024-07-04 22:21:20 +02:00
Ricki Hirner
56f7b4bbc5 Replace Apache Commons by native calls/Guava (#883)
* Replace Commons IO by native calls/Guava

* Drop other Apache Commons

* Fix tests
2024-07-04 16:00:29 +02:00
Ricki Hirner
4a3ebc422f Version bump to 4.4.1 2024-07-02 17:03:37 +02:00
Ricki Hirner
4b18302ec7 Fix JtxSyncer entry point 2024-07-02 15:25:12 +02:00
Ricki Hirner
5dd7609524 Fix tests 2024-07-02 14:06:19 +02:00
Ricki Hirner
48855c7bb8 [DI] Use constructor injection for SyncManager sub-classes (#874) 2024-07-02 12:59:42 +02:00
Ricki Hirner
7a8761f703 Version bump to 4.4.1-rc.1 2024-07-01 21:58:21 +02:00
Ricki Hirner
1dd91a2848 Update dependencies 2024-07-01 21:55:09 +02:00
Ricki Hirner
90f1c015d2 Fix "force read-only address books" setting in UI (bitfireAT/davx5#587)
* AccountScreen: take "force read-only address books" setting into account

* Collection screen: take "force read-only address books" setting into account

* Add KDoc

* Small naming change
2024-07-01 21:50:15 +02:00
Ricki Hirner
726e20ed52 Show CalDAV/CardDAV tab when the respective service is present (#868)
Use existence of CalDAV/CardDAV service as condition for whether to show the respective tab
2024-07-01 12:28:35 +02:00
Ricki Hirner
7f750e22cb Split syncadapter package (bitfireAT/davx5#586)
* Split syncadapter package into:

- sync: actual collection sync code and everything else
- sync.account: related to Android accounts
- sync.framework: Android sync framework integration
- sync.groups: contact group strategies
- sync.worker: sync workers (WorkManager)

* Remove unused file, fix imports
2024-06-30 14:42:40 +02:00
Ricki Hirner
6b6573ddd2 [CI] Don't run lint/unit tests on release variant 2024-06-29 10:05:59 +02:00
Ricki Hirner
8849b363c7 Fetch translations from Transifex 2024-06-29 09:57:17 +02:00
Ricki Hirner
005d6b30c0 Version bump to 4.4.1-beta.1; update dependencies 2024-06-29 09:56:08 +02:00
Ricki Hirner
c3436fd23f Mark UnifiedPush setting as experimental 2024-06-27 19:54:28 +02:00
Ricki Hirner
5497e343c0 Add Github dependency graph 2024-06-27 18:46:14 +02:00
Ricki Hirner
479a2c363a Optimize build cache; use Gradle 8.8 2024-06-27 18:22:14 +02:00
Arnau Mora
18b1e5222e Fix GMD tests (#867)
* Run CI

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed GPU to swiftshader

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added more options

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Cleaning managed devices

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed cleanup of GMD

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Cleaning cache

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed app clean

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removal of cached system image

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of cached image removal

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Disabled build cache

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Disabled configuration cache

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed run command

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Downgrade AGP

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Restore to original

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Upgrade AGP again

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Add setupTimeoutMinutes=180 option

* Use large runner

* Update gradle, add maxConcurrentDevices=1

* Remove maxConcurrentDevices=1 again

* Use gradle 8.7 again, clean caches before

* Create "compile" task that caches dependencies etc.

* Don't use incremental build cache anymore

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-06-27 18:12:55 +02:00
Ricki Hirner
d4b4981e26 Version bump to 4.4.1-alpha.3 2024-06-25 22:22:09 +02:00
Ricki Hirner
e1f3785bc6 Add ProGuard rule to work around androidx-lifecycle 2.8.x + Compose 1.6 crash 2024-06-25 22:22:09 +02:00
Arnau Mora
e92f261faf Replace AppIntro by Compose Pager (#848)
* Migrated into to compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of AppIntro

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Imports cleanup

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Imports cleanup

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed padding

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* When launching intro, going back closes the app

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added content description

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved IntroActivity.Model to IntroModel

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Given fixed padding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved intro composables together

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Do not create new task

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Minor changes

* Remove last XML styles that were required for AppIntro

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-06-25 15:12:25 +02:00
Ricki Hirner
ea035fa931 Version bump to 4.4.1-alpha.2 2024-06-25 14:52:51 +02:00
Ricki Hirner
8167e8e3cb Update dependencies 2024-06-25 14:49:57 +02:00
Ricki Hirner
bcc16e1ab6 Implement basic Push functionality (#856)
* Move PushRegistrationWorker to push package

* Add UP dependency

* [WIP] UnifiedPush basic implementation

* Handle endpoint unregistration

* [WIP] Parse push notification message

* Parse push message in PushMessageParser

* Sync only affected account on push message

* Only initiate sync when push message is about a syncable collection

* Push registration worker: log when there's no configured endpoint

* Handle invalid/non-XML push messages

* app settings: show UP endpoint
2024-06-25 14:07:29 +02:00
Ricki Hirner
28948485f6 Update AGP 2024-06-16 20:05:19 +02:00
Ricki Hirner
0985a99ed3 Update dependencies, remove unnecessary dependencies 2024-06-13 22:27:14 +02:00
Arnau Mora
1d7084b555 Add Push Subscription Management (#800)
* [wip] Create worker for push registration and call it from someplace in UI

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* [wip] Subscription registration request

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Don't enqueue push registration worker from UI

* Enqueue PushRegistrationWorker on collection changes

* Fix tests

* Update dav4jvm; Use new post method

* Remove obsolete context

* Add get and deleteAll methods to serviceRepository and update usages

* requestPushRegistration: make endpoint an argument

* Update push subscription fields in DB on successful registration

* Don't create notification channels in test class

* Remove workmanager init and provide empty set of listeners in tests

* Require network connection to run PushRegistrationWorker

* Move module declaration to a separate TestModules interface

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-06-13 13:12:59 +02:00
Sunik Kupfer
64563bbd3a Observable collections repository (#829)
* Call collection updates and setters only from repository

* Make collection repository changes observable

* Add kdoc

* Add basic test

* Extract RefreshCollectionsWorker; move some HomeSetDao calls to DavHomeSetRepository

* Replace more HomeSetDao calls

* Remove duplicate copyright notice

* Drop weak reference for observers

* Rename method

* Remove test service after run

* Verify notifying works with mockk

* Rename test

* Use construction injection

* Remove unused SettingsManager

* Remove obsolete mockk rule

* Use runBlocking instead of runTest

* Change to observer linkedList to mutableSetOf, remove synchronized calls

* Change to hilt multibinding

* Remove some unnecessary lines; allow empty set by Hilt

* CollectionListRefresher: delete collections using repository

* deleteRemote: call callback too; adapt KDoc

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-06-12 12:43:56 +02:00
Ricki Hirner
0bdeffe70d [Github] Issue template: fix label of feature request 2024-06-12 11:39:26 +02:00
Ricki Hirner
9e0772a9dd Update AGP 2024-06-11 09:21:35 +02:00
Ricki Hirner
70e56df80c Move repository test package 2024-06-10 13:40:33 +02:00
Ricki Hirner
b4756666b6 Extract RefreshCollectionsWorker; move some (HomeSet)Dao calls to repository (#845)
* Extract RefreshCollectionsWorker; move some HomeSetDao calls to DavHomeSetRepository

* Replace more HomeSetDao calls

* Remove unused SettingsManager
2024-06-10 12:16:33 +02:00
Ricki Hirner
74304bfe17 Version bump to 4.4.1-alpha.1 2024-06-09 23:02:23 +02:00
Ricki Hirner
25daa57a6f Show CalDAV/CardDAV/Webcal tab when there's at least one item (#839)
Show CalDAV/CardDAV/Webcal tab when there's at least one time
2024-06-08 18:44:35 +02:00
Ricki Hirner
a3ccdc2a46 Define toolchain version; update dependencies (#834) 2024-06-08 18:09:45 +02:00
Arnau Mora
ffefd519b6 [Google] Allow login with custom domains again (#833)
Modified emailWithDomain in UiState

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-06-07 10:11:00 +02:00
Ricki Hirner
4823d6d671 [CI] Update gh-release action 2024-06-05 11:45:10 +02:00
Ricki Hirner
9b5d5c982b Version bump to 4.4.0.1 2024-05-31 08:49:25 +02:00
Ricki Hirner
a03c83450d App settings: mention that verbose logs can be viewed in debug info (#824) 2024-05-31 08:47:52 +02:00
Ricki Hirner
5d97161c9b Update dependencies (including ical4android, closes bitfireAT/davx5#551) 2024-05-31 08:11:32 +02:00
Ricki Hirner
671c17376a PreferenceRepository.observeAsFlow: emit initial value (#822) 2024-05-30 10:09:34 +02:00
Ricki Hirner
1856a5d7ce Fix R8 rules 2024-05-29 17:25:05 +02:00
Ricki Hirner
9cb78429e3 Fetch translations from Transifex 2024-05-29 17:10:32 +02:00
Ricki Hirner
2b17692fd6 Version bump to 4.4 2024-05-29 17:07:10 +02:00
Ricki Hirner
19e69f2079 Version bump to 4.4-rc.1 2024-05-28 23:39:38 +02:00
Ricki Hirner
72b90655e6 Update dark theme 2024-05-28 23:38:18 +02:00
Ricki Hirner
cdf83dad37 MKCALENDAR: wrap supported components in <CALDAV:supported-calendar-co… (#816)
* MKCALENDAR: wrap supported compoents in <CALDAV:supported-calendar-component-set>

* MKCALENDAR/MKCOL body generation: use Property.Name from dav4jvm instead of own strings
2024-05-28 21:05:09 +02:00
Ricki Hirner
e6be7a659f Last M3 Tweaks (#817)
* [WIP] Colors

* Update navigation drawer

* Update colors

* [WIP] PermissionSwitchRow night mode

* Fix PermissionSwitchRow icon in night mode

* Use more intense colors for FABs
2024-05-28 20:58:51 +02:00
Ricki Hirner
34052368d8 Issue template: add "actual result" 2024-05-28 14:29:48 +02:00
Sunik Kupfer
08c695bf05 Fix last sync time not being set for address books (closes #810) (#812)
* Use correct account name when retrieving service

* Rename method, add kdoc

* Address books: require main account and use as Account

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-27 17:00:32 +02:00
Arnau Mora
d7221974ed Increased icon size and text size for accounts list (#813)
* Increased icon size and text size

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Adapt

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-27 16:28:34 +02:00
Arnau Mora
9a31835b1c Fix color picker size (#808)
* Fix color picker size

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed missing comment & import optimization

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added border

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Color picker: use OutlinedButton

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-27 14:53:39 +02:00
Ricki Hirner
4a49c8d6c2 Update dependencies 2024-05-25 21:26:09 +02:00
Sunik Kupfer
c493fbd349 Orange progress indicator bar (#803)
* Create ProgressBar composable with secondary default color

* Minor change

* Add import

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-25 20:21:33 +02:00
Arnau Mora
71c57fc00d Fix padding for show only personal (#807)
* Fixed padding for "Show only personal checkbox"

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Aligned text correctly

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-05-25 20:15:58 +02:00
Ricki Hirner
3044ff70aa Increase version code 2024-05-17 18:05:42 +02:00
Ricki Hirner
76a47fd017 Update theme and style again to make it look like it used to look 2024-05-17 18:04:56 +02:00
Ricki Hirner
621a8c419b Fetch translations from Transifex (again) 2024-05-17 15:19:18 +02:00
Ricki Hirner
c4cf0dc07b Fix English string (remove duplicate end tag) 2024-05-17 14:59:55 +02:00
Ricki Hirner
9216605106 Version bump to 4.4-beta.1 2024-05-17 14:18:48 +02:00
Ricki Hirner
d802d67e22 Fetch translations from Transifex 2024-05-17 14:18:28 +02:00
Sunik Kupfer
f95b853248 Fix setup through nextcloud app (intent) not working (#782)
* Select nextcloud login type on nextcloud setup intent

* Fix linting error

* Add documentation

* Move model creation to compose LoginScreen

* Minor changes
- Use boolean to decide on skipping startPage
- Move login type selection logic to login types provider

* Minor changes

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-17 14:10:28 +02:00
Ricki Hirner
3bcecd4fd6 Try different M3 theme 2024-05-17 13:10:39 +02:00
Ricki Hirner
b93b63024c Fix widget colors (#799) 2024-05-17 10:51:09 +02:00
Ricki Hirner
d9610b26bb Repair drawable/ic_storage_notify 2024-05-16 18:33:51 +02:00
Ricki Hirner
7c43bcc558 Version bump to 4.4-alpha.1 2024-05-16 18:22:17 +02:00
Ricki Hirner
30f9b117cc Remove unnecessary resources 2024-05-16 18:19:46 +02:00
Ricki Hirner
f2255e1d53 Drop M2 (everything is M3 now) (#797)
Drop M2
2024-05-16 17:56:18 +02:00
Sunik Kupfer
e2bfa8c56b Rewrite AccountSettingsActivity to M3 (#795)
* Extract composables

* Drop sub component previews and minor adjustment

* Fix preview

* Extract view model

* Switch to M3

* Extract URI to Constant

* Minor changes

* We alway have AccountSettings

* Replace LiveData by State

* Use Snapshot.withMutableSnapshot in reload

* Don't show empty OAuth setting

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-16 17:34:11 +02:00
Ricki Hirner
5cc29fc58a M3 tweaks (#796)
* Refine chips in collection list

* Refine PermissionSwitchRow

* Collections list

* Fix WelcomePage and IntroActivity background color in dark mode

* Fix RadioWithSwitch in dark mode

* Drawer handler: branding in dark mode
2024-05-16 16:45:40 +02:00
Sunik Kupfer
6f02669832 Rewrite AppSettingsActivity to M3 (#792)
* Extract composables

* Extract model and companion object

* Switch to M3

* Linting

* Drop previews for sub composables

* Minor adjustments for readability

* Minor changes

- use manual URL from Constants
- use M3 in some Composables

* Create PreferenceRepository (for now only for verbose logging)

* Move actual settings to model; M3 Composables

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-16 13:00:20 +02:00
Ricki Hirner
e11d511971 Update dependencies 2024-05-16 11:46:26 +02:00
Ricki Hirner
00eeb0e6d5 More DI using Hilt (#789)
* [WIP] More hilt

* Use assisted inject for AccountSettingsMigrations

* DavDocumentsProvider: inject CredentialsStore

* Create a new WebdavScope and scope caches and credentials store to it

* Fix CredentialsStoreTest
2024-05-15 17:57:41 +02:00
Sunik Kupfer
814d19e698 Rewrite WifiPermissionsActivity to M3 (#791)
* Extract composables

* Extract view model and create preview

* Switch to M3 and linting

* Optimize imports

* Minor changes

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-15 16:57:48 +02:00
Sunik Kupfer
86252f9117 Rewrite DebugInfoActivity to M3 (#744)
* Convert M2 calls to M3

* Extract composable to screen

* Extract viewmodel

* Make screen model independent

* Use only primitive types in screen

* Introduce uiState class and switch to compose state where easy

* Switch remaining live data to compose state

* Add kdoc

* Add scrolling, adapt buttons to M3

* Move Intent logic to Activity

- create/handle Intent in Activity (may be replaced by NavGraph in future)
- Activity: pass unpacked initial data to Screen
- Screen: use hiltViewModel (adds hilt-navigation-compose dependency) to
  create model with initial data
- Screen: use Column instead of LazyColumn

* Fix test

* Optimize imports

* Minor changes

* Move AppTheme, fix showDebugInfo

* View instead of share logs; make local/remote resource smaller; make remote resource selectable

* Leave space for scrolling down past the FAB; don't show "Local resource: null"

* Re-order composables

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-15 16:43:23 +02:00
Arnau Mora
b5334887e8 Added M3 theme to intro pages (#756)
* Added M3 theme

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Don't use M2 colors anymore

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-15 15:59:53 +02:00
Arnau Mora
6b863164a4 Rewrite BatteryOptimizationsPage to M3 (#747)
* Migrated to M3

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Arch update for best practises

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved composable to individual file

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Renamed function

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved ViewModel

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved ViewModel

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved ViewModel-based Composable

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Created `UiState`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed model name

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Typo

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed checkboxes

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Make checkboxes clickable too

* Optimized imports

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Optimize imports

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2024-05-15 14:11:22 +02:00
Sunik Kupfer
b4e58eeb44 Log last sync time per collection service and URL (#702)
* Log last sync time per collection service and URL

* Remove unused deprecated method

* Include tasks and other services as caldav type
2024-05-14 21:28:18 +02:00
Arnau Mora
a246046f41 AccountSettingsActivity: make canAccessWifiSsid live-capable (#732)
* Added `canAccessWifiSsidLive`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed initial state

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Cleaned up code

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* More cleanup

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed inspection check

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Renamed function

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Using `broadcastReceiverFlow`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Simplified check

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Using `MODE_CHANGED_ACTION`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Updated comment

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Set default value for immediate

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Use derivedStateOf instead of produceState; correctly collect broadcastReceiverFlow

* Always show WifiSSID Card in Preview (otherwise Preview won't render)

* Don't call flow.map in Composable

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-11 14:49:56 +02:00
Ricki Hirner
0552bcab4a Version bump to 4.3.17-alpha.4 2024-05-09 21:28:38 +02:00
Ricki Hirner
3681507582 Inject SyncDispatcher over Hilt (#784)
* Inject SyncDispatcher over Hilt

* Use setWorkerFactory for TestListenableWorkerBuilder

* Correctly inject SyncDispatcher over an AssistedFactory
2024-05-09 21:20:38 +02:00
Arnau Mora
364f372a8b Rewrite TasksIntroPage to M3 (#760)
* Migrated to M3

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed theme

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved Composables and Model to individual files

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Move AppTheme to screen composable

* Fixed model name

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Minor re-ordering

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-09 18:51:38 +02:00
Ricki Hirner
857309c451 Correctly handle DeadObjectException (bitfireAT/davx5#578, #591)
* Soft fail sync on DeadObjectException so that it is retried without immediate error message

* Handle DeadObjectException (→ retries sync); Syncer: generalize all-catch
2024-05-09 13:52:50 +02:00
Sunik Kupfer
ad24dd54c7 Fix login detail pages loosing user changes on rotation / re-creation (closes #775) (#778)
Inject view models in composables using hilt
2024-05-07 10:17:25 +02:00
Ricki Hirner
952cb52b95 Don't listen for account changes all the time (#780)
* Delete account: don't rely on cleanup worker

* Don't list for account changes all the time
2024-05-04 20:52:50 +02:00
Ricki Hirner
1ad8e892b6 Intro pages: use Hilt for dependency injection (#779)
* Accounts: move syncAll to model; better Hilt usage

* Move calculation of whether IntroActivity is shown into AccountsModel

* Intro pages: inject dependencies with Hilt

* Fix preview
2024-05-04 20:10:36 +02:00
Arnau Mora
4cffbe7b40 Rewrite PermissionsIntroPage to M3 (#758)
* Migrated to M3

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Replaced preview theme

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed theme

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Forced all sets to be private

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved Composables and Model to individual files

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed naming

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Theme / M3 changes

* Observe lifecycle from within Screen

* Minor changes

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-04 13:46:32 +02:00
Ricki Hirner
e6eb90861e Use hiltViewModel for creating ViewModels with parameters in new Screen architecture (#776)
* Add hilt-navigation-compose dependency

* AccountScreen: use hiltViewModel

* Use hiltViewModel for CollectionScreen and CreateCollectionScreens
2024-05-03 14:46:46 +02:00
Ricki Hirner
9005121b52 Update dependencies (including AGP) 2024-05-03 12:23:58 +02:00
Arnau Mora
a7c04c2bf7 Replace DavUtils.lastSegmentOfUrl by UrlUtils.lastSegment (#767)
* Added `ReplaceWith` to `lastSegmentOfUrl`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Replaced all usages

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Rollback replacement

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added tests for UrlUtils

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of `DavUtils.lastSegmentOfUrl`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Turned into variable

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed method call

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed method call

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-05-03 11:29:05 +02:00
Ricki Hirner
39f8f2e475 Add issue templates 2024-05-02 14:03:01 +02:00
Sunik Kupfer
9c6c95a249 Start at LoginDetails page if logging in via intent (#772)
* Start at LoginDetails page if login via intent has data

* Ignore intent on re-creation
2024-05-02 12:43:08 +02:00
Sunik Kupfer
aafcb2e94a Pre-select per-contact categories for login type NextcloudLogin (#774)
* Pre-select per-contact categories for login type NextcloudLogin

* Update the group method unconditionally to suggested group method
2024-05-02 12:32:23 +02:00
Arnau Mora
e40fa6e0fb Switched CalDAV and CardDAV tab positions (#769)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-05-02 11:18:30 +02:00
Arnau Mora
c9fd66bd63 Rewrite OpenSourcePage to M3 (#755)
Migrated to M3

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2024-05-02 11:11:51 +02:00
Arnau Mora
99cf0eca7a Migrated to M3 (#754)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2024-05-02 11:08:37 +02:00
Ricki Hirner
2b2476b4bc Rewrite RenameAccountDialog to M3 (#768) 2024-04-30 14:37:12 +02:00
Arnau Mora
81834ca0db AccountsScreen: move navigation drawer to foreground so that it overlays FABs when open (#765)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-04-30 14:20:52 +02:00
Ricki Hirner
0a4a06a50a Minor lint 2024-04-30 11:49:01 +02:00
Ricki Hirner
40795bf5c0 Version bump to 4.3.17-alpha.3 2024-04-30 11:40:40 +02:00
Ricki Hirner
40034fe400 Update dependencies 2024-04-30 11:27:26 +02:00
Ricki Hirner
b13c6b0e6f Rewrite AccountActivity to M3 (#752)
* [WIP] Separate AccountScreen, use M3 elements

* [WIP] Use UseCases for complex flow calculations

* [WIP] Move account deletion logic into AccountRepository

* Move rename operation to repository

* Adapt FABs

* Don't use snackbars to show when the collection list is refreshed

* New collection list layout (bitfireAT/davx5#159)

* [WIP] Create AccountModel from within screen

* [WIP] Clean up AccountScreen

* [WIP] CreateAddressBook

* [WIP] Create address book / calendar screen

* [WIP] Begin CollectionScreen

* [WIP] CollectionScreen

* Error handling

* String resources

* Optimizations, remove unnecessary things
2024-04-30 11:25:29 +02:00
Ricki Hirner
c33ea84c77 Rewrite AccountsActivity to M3 (#749)
* [WIP] Rewrite AccountsActivity to M3

* [WIP] AccountsScreen: FAB, previews

* [WIP] Warning cards

* Adapt FABs
2024-04-26 11:20:55 +02:00
Ricki Hirner
0c748ebe73 Provide snackbar to LoginTypesProvider (backport of bitfireAT/davx5#576) 2024-04-24 10:31:46 +02:00
Ricki Hirner
fda96ac653 Version bump to 4.3.17-alpha.2 2024-04-23 17:42:07 +02:00
Ricki Hirner
597c572f24 Login: allow changing account name when suggestions are expanded (bitfireAT/davx5#573) 2024-04-23 17:42:03 +02:00
Ricki Hirner
71b0912494 Login: allow "Couldn't create account" snackbar more than once (bitfireAT/davx5#572) 2024-04-23 17:41:59 +02:00
Ricki Hirner
f853019f47 LoginScreenModel: initialize account name over setter (bitfireAT/davx5#571) 2024-04-23 17:41:54 +02:00
Ricki Hirner
0b212fc6bd WebDAV Mounts UI: M3, refactoring (#736)
* WebDavMountsScreen: M3, refactoring

* [WIP] AddWebDavMount

* Use M3 pull-to-refresh

* AddWebDavMount, move logic to WebDavMountRepository

* Show loading state in LinearProgressBar

* Add WebDAV mount: IME action and focus requester

* Move "test WebDAV" logic from model to repository

* Move "refresh quota" logic to repository

* Move querying and deleting mounts to repository; IME navigation

* KDoc

* Move hasWebDav tests to repository
2024-04-23 17:34:20 +02:00
Sunik Kupfer
3fbffc4a72 RefreshCollectionsWorker causes app to crash when the service is invalid (#743)
* RefreshCollectionsWorker: Ignore requests with invalid service IDs

* Update KDoc
2024-04-22 17:31:55 +02:00
Ricki Hirner
b17c39c370 Version bump to 4.3.17-alpha.1; fix LoginActivity upwards navigation 2024-04-21 16:51:16 +02:00
Ricki Hirner
019dde6ef9 Rewrite login to M3 and with better UI states (bitfireAT/davx5#567)
* [WIP] Rewrite login to M3 and better UI states

* Use AccountRepository to create account

* LoginModel is SSOT for page navigation

* Support forced group method

* Show progress bar when account is being created

* Make account name suggestions work again

* Use M3 text field supportText for errors

* Refactor: login by URL, login by email, advanced login

* Refactor Nextcloud login, move login flow logic to separate class

* Refactor Google login, move OAuth logic to separate class

* Fix errors when navigating back after successful resource detection

* Make PasswordTextField M3

* ManagedLogin: M3, UiState

* Updated theme; managed login functionality

* Improve back navigation
2024-04-21 16:27:54 +02:00
Ricki Hirner
34b88c3ad8 Add M3 theme and apply to AboutActivity (#731)
* Rename AppTheme to M2Theme, add M3 theme

* Rewrite AboutActivity to M3

* Apply M3 theme; minor optimizations

* Use M3 version of AboutLibraries

* Use material3 instead of material3-android dependency

* Use reversed theme
2024-04-19 11:05:39 +02:00
Ricki Hirner
a1d85c4c9b Version bump to 4.3.16.1 2024-04-17 13:25:52 +02:00
Ricki Hirner
8c12300be3 Update dav4jvm to support relaxed parsing (bitfireAT/davx5#563)
* Update dav4jvm

* Add test
2024-04-17 13:25:48 +02:00
Ricki Hirner
21d1020662 Add PR template 2024-04-16 20:10:58 +02:00
Ricki Hirner
80a54de015 Add PR labels for automatic release notes 2024-04-15 11:43:42 +02:00
Ricki Hirner
a3a4a72012 Fix test_observerFlow_updatedValue (use Set instead of List) 2024-04-15 11:12:02 +02:00
Ricki Hirner
a165c97166 Version bump to 4.3.16 2024-04-14 21:55:33 +02:00
Arnau Mora
bd93c86e2c Adjusted paddings (#707)
* Adjusted paddings

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Unify intro card padding to 8 dp

* Minor changes in Composable calls

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-04-14 21:42:52 +02:00
Ricki Hirner
ab4244a533 Allow to specify export flags for broadcast receivers; log incoming broadcasts (#729) 2024-04-14 21:17:19 +02:00
Ricki Hirner
0a58f0a269 Always use provider.use to automatically close content provider clients (#726) 2024-04-13 22:03:42 +02:00
Ricki Hirner
f6d71ffb98 Fetch translations from Transifex 2024-04-13 21:19:42 +02:00
Ricki Hirner
0ff24a8ce2 Version bump to 4.3.16-rc.1 2024-04-13 21:05:32 +02:00
Ricki Hirner
114084f405 Use LocalContentColor for ClickableTextWithLink (#725) 2024-04-12 18:02:25 +02:00
Ricki Hirner
c3b3dd4e35 Add non-Compose colorBackground for AppIntro (#724) 2024-04-12 17:29:55 +02:00
Ricki Hirner
cd8023b24b [CI] Make sure concurrency name doesn't have spaces 2024-04-12 17:18:27 +02:00
Ricki Hirner
ee36753e1a TasksActivity: use defaultValue=true for HINT_OPENTASKS_NOT_INSTALLED (#723) 2024-04-12 17:08:37 +02:00
Ricki Hirner
c0570549c9 Use broadcastReceiverFlow instead of BroadcastReceivers (#722)
* [WIP] Use broadcastReceiverFlow and packageReceiverFlow

to make sure that broadcast receivers are unregistered

* Rewrite remaining BroadcastReceivers to use broadcastReceiverFlow

* TasksAppWatcher: minor coroutine changes

* KDoc

* TasksActivity: use Compose state instead of LiveData
2024-04-12 16:52:23 +02:00
Ricki Hirner
463c18c4fb Update dependencies, including AGP 2024-04-10 14:10:12 +02:00
Sunik Kupfer
72320b30f7 Check if keep-permissions changed when user comes back from settings app (#715)
* Check if keep-permissions changed when user comes back from settings app

* Use one model only

* Use mutable state flows

* Use compose mutable state

* [CI] Improve caching behavior

* Freshly created model can never be null

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-04-10 14:06:48 +02:00
Ricki Hirner
dd08415c84 [CI] Improve caching behavior 2024-04-10 13:54:23 +02:00
Ricki Hirner
b88c35169e SettingsManager: use Flows instead of LiveData (#714)
* SettingsManager: use flows instead of LiveData

* Fix tests
2024-04-10 12:10:03 +02:00
Ricki Hirner
1cd0df1e6a Rename dev-ose branch to main-ose 2024-04-09 18:16:10 +02:00
Ricki Hirner
74e89bde9b Version bump to 4.3.16-beta.2 2024-04-09 17:56:41 +02:00
Ricki Hirner
b72cc7f9fb [CI] Fix test workflows (main branch condition, cache names) 2024-04-09 17:44:18 +02:00
Ricki Hirner
3023a935a8 Detect WebDAV-Push support (#716)
* Detect WebDAV-Push support

- detect and save supportsWebPush and pushTopic to database

* Collection info: show Push support + subscription time
2024-04-09 17:31:02 +02:00
Ricki Hirner
2c5292a52b Update dependencies 2024-04-09 15:38:41 +02:00
Ricki Hirner
a4ce1f93f0 Nextcloud Login Flow: improved handling of back (#712)
* Use Compose state instead of LiveData; reset token before starting Login flow

* [WIP] Reset state variables when needed

* Always run postForJson in IO dispatcher; show progress when polling for login data

* Reset login URL in onReturnFromBrowser
2024-04-09 11:52:49 +02:00
Ricki Hirner
30d4aa2e73 [CI] Fix tests 2024-04-09 10:33:38 +02:00
Ricki Hirner
e638f5debc Create account: user feedback when account name is already taken (#701)
* Show snackbar when account can't be created without known reason; show error when account name is taken

* Don't crash on empty account name
2024-04-04 13:36:27 +02:00
Ricki Hirner
7b2f14d148 Use normal runner for tests with emulator
See https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/
2024-04-03 12:38:56 +02:00
Ricki Hirner
3385e5ddb7 Fix tests 2024-04-02 10:58:37 +02:00
Ricki Hirner
44d2446d22 Better handling of address book accounts without main account (closes #694) 2024-04-01 21:26:02 +02:00
Ricki Hirner
6b88052e10 DeleteCollectionDialog: handle case that result may contain null value (#697) 2024-04-01 21:10:53 +02:00
Ricki Hirner
6d462ea9e4 Update gradle, Hilt 2024-04-01 13:32:50 +02:00
Ricki Hirner
10b007bd15 Version bump to 4.3.16-beta.1 2024-03-28 14:26:57 +01:00
Ricki Hirner
9b7a1dbe87 Update ical4android 2024-03-28 13:52:37 +01:00
Sunik Kupfer
6ff0ebc7e2 Do not show existing password on password change (#689)
* Do not show existing password when changing

* Use PasswordTextField for password input dialogs

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-28 13:34:32 +01:00
Ricki Hirner
9b47427b66 SettingsLiveData: post initial value even when it's null (#686) 2024-03-28 09:44:19 +01:00
Ricki Hirner
d0e5bbc0ad Update copyright; upgrade dependencies (#687) 2024-03-27 17:32:35 +01:00
Ricki Hirner
f825abca27 Login screen: increase padding of login options minimally 2024-03-27 17:27:46 +01:00
Arnau Mora
7b12a53616 Create a native Material theme and get rid of XML styles (#675)
* Added custom Compose theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of accompanist theme adapter

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed unused import

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added actionbar to the activities that didn't have one

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Theme now always hides actionbar

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added back string

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of all color definitions

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved color definition to drawable

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using Compose colors

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using AppCompat theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed XML theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved color definition

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added bars coloring in Compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added dark theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added custom Compose theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of accompanist theme adapter

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed unused import

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added actionbar to the activities that didn't have one

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Theme now always hides actionbar

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added back string

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of all color definitions

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved color definition to drawable

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using Compose colors

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using AppCompat theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed XML theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved color definition

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added bars coloring in Compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added dark theme

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed content description

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using `onSupportNavigateUp`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed color on top of primary green

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added up navigation

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Made `onSupportNavigateUp` optional

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Typo

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of edge-to-edge

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added back some XML styles

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Simplify TasksCard calling

* Move theme colors to flavor-specific ThemeColors file

* Remove global AppTheme paddings for now

* Optimize imports

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-27 17:21:02 +01:00
Ricki Hirner
8507c64be1 OpenSourcePage: make "dontShow" LiveData (#683) 2024-03-27 14:42:44 +01:00
Sunik Kupfer
557b5653e5 Don't show empty suggested account names dropdown (bitfireAT/davx5#555)
* Don't show empty suggested account names dropdown

* Hide the trailing dropdown icon if suggested account names list is empty
2024-03-27 13:04:50 +01:00
Ricki Hirner
7760cbaa72 Show Pending state in AccountsActivity, too; remove obsolete code (#680) 2024-03-27 10:13:52 +01:00
Sunik Kupfer
14ef74e231 Don't move focus downward without reason (bitfireAT/davx5-ose#553) 2024-03-26 10:20:45 +01:00
Ricki Hirner
78c148615a Fix ProGuard rules 2024-03-24 21:45:44 +01:00
Ricki Hirner
b63cd67e9c Backport changes from build variants 2024-03-24 21:30:08 +01:00
Ricki Hirner
079c3efdfd Rewrite login activity to Compose (#672)
* Remove unnecessary layout files

* [WIP] Rewrite LoginActivity to Compose

* [WIP] Login type

* [WIP] Login by URL, Google, Nextcloud

* Remove unnecessary files and kapt

* More renaming and removing of unnecessary files

* Login with email, URL

* Login type: Advanced

* Drop "known base URLs"

* "Detect resources" and "Create account" page

* Introduce LoginTypesProvider interface
2024-03-24 18:25:30 +01:00
Michael Biebl
014c94a031 Do not show extended proxy details in App Settings if proxy type is system or none (#661)
Fixes: #660
2024-03-21 11:44:03 +01:00
Arnau Mora
5921fb2bb6 Upgraded ical4android (#663)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-03-21 11:43:44 +01:00
Arnau Mora
58b02c04ef Replaced icons with auto-mirrored version when possible (#666)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-03-21 11:41:47 +01:00
Ricki Hirner
06e7eeb391 Don't use Gradle build and configuration cache for releases (#662)
* Don't use Gradle build and configuration cache for releases

- don't enable Gradle build and configuration cache for the project, but recommend it for the developer
- explicitly enable Gradle build and configuration cache for CI test jobs
- let AboutLibraries generate lib definitions itself again
- also don't archive test results (sometimes fails and we never use the results)

* Add encryption key for gradle cache

* Only warn on configuration cache problems (caused by AboutLibraries)
2024-03-20 15:29:21 +01:00
Michael Biebl
6c882877d0 Remove unused string resources (#651)
The list was computed in an automated way and includes:

intro_battery_not_whitelisted
intro_battery_whitelisted
permissions_jtx_status_off
permissions_jtx_status_on
about_flavor_info
about_translations_thanks
install_email_client
accounts_global_sync_disabled
accounts_global_sync_enable
app_settings_tasks_provider_synchronizing_with
account_no_address_books
account_no_calendars
account_no_webcals
account_swipe_down
account_create_new_address_book
account_create_new_calendar
settings_title
settings_enter_username
settings_enter_password
settings_key_default_alarm
certificate_notification_connection_security
trust_certificate_unknown_certificate_found
2024-03-20 15:24:00 +01:00
Ricki Hirner
d8e6f82104 Fix import 2024-03-20 15:17:20 +01:00
Arnau Mora
28ddf5c86a Provide a widget option (#643)
* Added Glance dependency

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Defined Glance widget

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Replaced with new worker

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Rename ui/widget to ui/composable, move sync widget into ui/widget; adapt strings

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-20 12:37:57 +01:00
Ricki Hirner
c3bf95fa5c Update AGP to 8.3.1 2024-03-20 11:41:13 +01:00
Arnau Mora
d37718c58a Rewrite CreateCalendarActivity to Compose (#645)
* Added color picker dialog

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed title

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Migrated to Jetpack Compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed unused layouts

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed activity name

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed duplicated title

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Cleanup

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Error tweaks

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Rewrite Create address book, Create collection, Delete collection

* [WIP] Create calendar: more properties; use own color picker (other one was M3)

* [WIP] Create calendar

* Add missing properties for calendars

* Support color, remove comments

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-20 11:40:03 +01:00
Ricki Hirner
fea33ab60a Version bump to 4.3.15 2024-03-16 15:18:33 +01:00
Ricki Hirner
3ab278a315 Better handling of tasks app changes (#652)
* Fix task sync update when new app is selected by user or TasksWatcher
* Minor refactoring, KDoc
2024-03-16 12:16:23 +01:00
Ricki Hirner
322a7565b0 TaskUtils: take updateSyncSettings into account 2024-03-16 00:26:15 +01:00
Ricki Hirner
732d925b4c Show pending syncs in AccountActivity again 2024-03-15 12:08:40 +01:00
Ricki Hirner
a43dbb5cff Have network/WiFi check in both PeriodicSyncWorker and OneTimeSyncWorker; add tests
- OneTimeSyncWorker is also started by sync framework, so it should take network restrictions into consideration
- add "manual" flag for manual syncs that ignore network restrictions
2024-03-15 11:30:54 +01:00
Ricki Hirner
fe833759ee Fetch translations from Transifex 2024-03-15 10:38:37 +01:00
Ricki Hirner
53bf342822 Version bump to 4.3.15-beta.1 2024-03-14 20:24:36 +01:00
Ricki Hirner
30122a79f3 Periodic workers directly run sync (#648)
* [WIP] Don't create a separate SyncWorker for every sync (run directly within onetime/periodic sync instead)

* [WIP] address books

* Account(s)Activity: don't show pending workers

* Migration to set new periodic sync worker tags

* Fix tests

* ContactsSyncAdapter issues address book sync on main account (not contacts sync)

* SyncAdapterService: optimize blocking with Flow instead of LiveData
2024-03-14 20:08:38 +01:00
Ricki Hirner
a16fd468fd TasksIntroPage: fix "Dont show again" 2024-03-13 17:19:23 +01:00
Ricki Hirner
76df2b320d Account settings UI: fix "sync only manually" summary 2024-03-13 17:03:50 +01:00
Ricki Hirner
d6ff27fcc8 Fix up navigation 2024-03-13 17:02:27 +01:00
Ricki Hirner
2e780a890b Rewrite AccountSettingsActivity to Compose (#646)
* AccountSettings: rewrite Sync settings
* Authentication
* CalDAV, CardDAV settings
2024-03-12 20:48:06 +01:00
Ricki Hirner
45d6b33023 Add stats parameters for Web site calls, rename AccountActivity2 back to AccountActivity 2024-03-12 15:41:50 +01:00
Ricki Hirner
4486c0862a Move "Community" to "Support the project", remove obsolete code 2024-03-12 15:41:41 +01:00
Ricki Hirner
33e726a7b0 Rewrite navigation drawer to Compose (#644)
(sync bitfireAT/davx5#545)
2024-03-12 12:55:38 +01:00
Ricki Hirner
75a0c77b5f Rewrite WifiPermissionsActivity to Compose (#640) 2024-03-11 16:23:25 +01:00
Ricki Hirner
06b4cf9477 Refactor Tasks app detection (and settings update when tasks apps change) (#637)
* Refactoring

* Better live handling of (un)installed task apps

* Minor changes

* SettingsManager: explicitly mark possibility of null LiveData values

* Fix tests
2024-03-11 13:51:46 +01:00
Ricki Hirner
cb56132994 Make LinearProgressIndicators orange (#636) 2024-03-10 23:08:22 +01:00
Ricki Hirner
9742913a3e App settings: show "Battery optimizations" dialog even when DAVx5 is already exempted 2024-03-10 22:44:52 +01:00
Ricki Hirner
ea66838cd6 Version bump to 4.3.15-alpha.2 2024-03-10 20:52:33 +01:00
Ricki Hirner
377c0344da Improve homepage URL launching (#632)
* Move homepage and other Web URLs to Constants; minor refactoring

* Use AppTheme with built-in safe LocalUriHandler instead of MdcTheme; minor refactoring

* Account settings: add TODO for Compose rewrite

* Use UriHandler instead of UiUtils.launch when possible
2024-03-10 20:51:40 +01:00
Ricki Hirner
af5c732adc Update dependencies 2024-03-10 19:27:37 +01:00
Ricki Hirner
c8a0128842 Rewrite app settings to Compose (#628)
* [WIP] Rewrite app settings to Compose

* Optical changes

* Add Help button

* Fix URL, preferences LiveData: handle null value

* Fix tests
2024-03-10 19:20:11 +01:00
Arnau Mora
66f0075cc9 Rewrite AddWebdavMountActivity to Compose (#630)
* Migrated AddWebdavMountActivity to Compose

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Minor changes, use PasswordTextField

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-10 18:33:22 +01:00
Ricki Hirner
3edcc02a21 Minor classes refactoring 2024-03-09 17:42:38 +01:00
Ricki Hirner
77ab1801fa Move Hilt SyncComponent 2024-03-09 16:29:55 +01:00
Ricki Hirner
d9ddfafbf9 IntroActivity: get IntroPageFactory over Hilt 2024-03-09 14:37:33 +01:00
Ricki Hirner
c5adc93d1a Bump version to 4.3.15-alpha.1 2024-03-09 14:27:37 +01:00
Ricki Hirner
42f99e644d Rewrite intro pages to Compose (dropping all Fragments) (#626) 2024-03-09 14:25:24 +01:00
Arnau Mora
1cbfedc9e4 Rewrite PermissionsActivity, PermissionsFragment to Compose (#583)
* Migrated `PermissionsFragment` to Compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Migrated `PermissionsActivity` to Compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed preview

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed TODO

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Don't show unavailable permissions, explicitly pass Activity model, minor changes

* Added status text

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Increased margins

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Button uppercase, safeguard Keep Permissions launch

* Moved tasks app availability to viewmodel

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added tasks watcher back

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed unnecessary livedata

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Cleanup

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-08 16:35:29 +01:00
Ricki Hirner
962dab7cf2 Rewrite AccountActivity to Compose (#617)
* [WIP] Add AccountActivity2 in Compose

* Make paging collections work when data changes

* [WIP] Add ProgressIndicator TODO

* [WIP] CardDAV: add swipe-to-refresh

* [WIP] Correctly use Pager

* [WIP] Only show Webcal tab when there are subscriptions

* [WIP] Implement collection properties dialog

* Implement "create collection" and "show only personal collection"

* [WIP] Add collection overflow menu items

* Show color as left border, max. 2 icons per row

* [WIP] Delete collection dialog

* Add "delete collection"

* Implement "Force read-only"

* Delete old XML classes and resources

* Add permissions warning

* Implement "Rename account"

* Case-insensitive sorting, minor changes

* Horizontal arrangement

* Less integration of Webcal subscriptions (other layout)

* Accessibility

* Collection list: provide ID als key for lazy list

* Only show "Create addressbook/calendar" when there's at least one writable homeset
2024-03-08 16:32:55 +01:00
Arnau Mora
2e669812b1 Rewrite WebdavMountsActivity to Compose (#607)
* Migrated to Compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Text hides when there are mounts

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed todo

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Migrated to Compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Text hides when there are mounts

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed todo

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed vertical scroll

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added action for ClickableText

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed indentation

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed layout

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed overflow preferences

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed padding

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed link color

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed preview

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed back arrow

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Require explicit Context for helpUrl to make it work in Compose preview

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-03-08 16:29:16 +01:00
Ricki Hirner
7e743f2dbd Move SyncComponent to syncadapter package 2024-03-05 16:09:55 +01:00
Ricki Hirner
6f08901f04 Add permissions for CI Github release action to allow creating a discussion 2024-03-04 17:04:11 +01:00
Ricki Hirner
f06eef2e72 Fetch translations from Transifex 2024-03-04 14:19:47 +01:00
Ricki Hirner
f06d396dd8 Bump version to 4.3.14 2024-03-04 14:16:50 +01:00
Ricki Hirner
3004bf14bc Update dependencies 2024-03-04 14:16:40 +01:00
Ricki Hirner
b511c04493 Bump version to 4.3.14-beta.2 2024-03-02 00:00:59 +01:00
Ricki Hirner
1337c8d648 Update ical4android 2024-03-02 00:00:18 +01:00
Ricki Hirner
e3485ec3ec Use gradle-managed device for testing (#609) 2024-03-01 20:15:45 +01:00
Ricki Hirner
dc1c77c336 Update APG and dependencies 2024-03-01 11:49:59 +01:00
Ricki Hirner
6b9395c254 Adapt WelcomeFragment for smaller devices (#606) 2024-02-29 00:18:58 +01:00
Arnau Mora
e24543a298 Rewrite WelcomeFragment to Compose (#582)
* Migrated to Jetpack Compose

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Simplify layout, remove animations

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-02-28 17:35:14 +01:00
Ricki Hirner
334fdb5953 Disable periodic sync workers that don't exist anymore (#602)
* Disable old periodic worker when renaming accounts

* Periodic sync worker now disables itself when an account is not available anymore

* Add test
2024-02-28 13:38:45 +01:00
Sunik Kupfer
86742f5b18 Don't upload event when calendar is read only (#587)
* Make readOnly a LocalCollection property

* Move readOnly detection to SyncManager

* Add readOnly state access to LocalCalendar

* Add not implemented error to readOnly state access of LocalJtxCollection

* Handle read-only state of calendar at dirty events upload

* Handle read-only state of calendar at processing of locally deleted events

* Remove todo and update kdoc

* Fix indenting

* Add read-only prop to LocalTestCollection

* Add read-only state access to LocalTaskList

* LocalTestCollection: don't set read-only

* Update ical4android (for new KDoc)

* Make LocalCollection readOnly-API read only and take value from content provider during populate()

* SyncManager: use readOnly direct from localCollection

* Lift resetDeleted up to LocalResource

* Override and use resetDeleted for LocalEvent

* Add resetDeleted to LocalJtxICalObject

* Add resetDeleted to LocalTask

* Add resetDeleted to LocalTask

* Add resetDeleted to LocalTestResource

* Provide default access level

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-02-27 13:00:40 +01:00
Arnau Mora
df2b7d2bd0 Fixed span styles for URL annotations (#598)
* Fixed span styles

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Pushing initial style to builder

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-02-26 15:44:19 +01:00
Ricki Hirner
4c1d9d21bd Releases: generate release notes and discussion thread automatially (#596) 2024-02-26 12:41:04 +01:00
Arnau Mora
be309e15b3 Rewrite BatteryOptimizationsFragment to Compose (#580)
* Migrated to Jetpack Compose

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added `observeBoolean`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Simplified settings interaction

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Migrated to Jetpack Compose

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added `observeBoolean`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Simplified settings interaction

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Use SafeAndroidUriHandler instead of UiUtils.launchUri

* Removed animation for manufacturerWarning

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed animation for manufacturerWarning

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added `getBooleanLive`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Using `getBooleanLive`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved UI definitions to file scope

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Don't use specific times for waiting in tests

* Renamed function

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* More exact naming

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-02-23 14:12:02 +01:00
Ricki Hirner
6b1367d6dc Reflect ical4android tasks API changes 2024-02-22 12:16:10 +01:00
Ricki Hirner
774fced9e8 Fetch translations from Transifex 2024-02-22 12:07:32 +01:00
Ricki Hirner
dd9681e75a Bump version to 4.3.14-beta.1 2024-02-22 12:06:23 +01:00
Ricki Hirner
bc3dbf09fc Update dependencies 2024-02-22 12:04:51 +01:00
Ricki Hirner
cae1ed5efb More correct usage of expedited workers (#566)
* Add foreground notification type to expedited workers (required for Android 14)

* Make SyncWorker a long-running worker

* Don't use expedited SyncWorker for everything; handle foreground service launch restriction

* AddressBookSyncer: only request expedited for sub-jobs when parent job is expedited, too

* RefreshCollectionsWorker is not long-running -> no foreground service type needed

* Fix tests

* Don't use foreground service type in ForegroundInfo

* Make SyncWorker not long-running
2024-02-22 11:56:44 +01:00
Ricki Hirner
e5cf7610ad OpenSourceFragment: use correct string resource 2024-02-21 16:47:56 +01:00
Arnau Mora
0253cd3d89 Migrate to Kotlin DSL (#586)
* Migrated to Kotlin DSL

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Migrated to Kotlin DSL

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Update versions, suppress nofications for libs we don't want to upgrade

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-02-20 11:48:02 +01:00
Ricki Hirner
30bb981975 Don't filter translators by role (but exclude "bitfire" user) (bitfireAT/davx5#532) 2024-02-20 10:00:03 +01:00
Arnau Mora
f6ac4e02d6 Rewrite OpenSourceFragment to Compose (#581)
* Migrated to Jetpack Compose

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed layout file

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Adapt paddings and font size

* Added check for non-available browsers

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Add SafeAndroidUriHandler

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-02-19 19:09:49 +01:00
Ricki Hirner
af00ff0c4c Update dependencies 2024-02-16 11:30:08 +01:00
Arnau Mora
8ac95e0796 Use gradle version catalog (#564)
* Using version catalog

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Fixed library names

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Remove lifecycle-ext

* Rename, reorder

* Remove unnecessary entries

* Use BOM for Compose again

* Try to fix BOM

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-02-15 11:21:09 +01:00
Ricki Hirner
5a5023bf54 Bump version to 4.3.13.1 2024-02-14 17:09:50 +01:00
Ricki Hirner
ea6e520c93 Drop foreground service (#569)
* Remove foreground service permissions

* Remove foreground service + setting

* Remove unused setting name and notification IDs
2024-02-14 17:05:53 +01:00
Arnau Mora
a7f6192177 Rewrite CreateAddressBookActivity to Compose (#559)
* Added initial layout

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Improved UI

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Replaced autofill with radio buttons

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of unnecessary errors

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of null indicators

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added default home set selection

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Minor UI changes

* Drop displayNameError

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-02-12 13:28:11 +01:00
Sunik Kupfer
3dd63df5c8 Rewrite RenameAccountFragment to compose (closes #478) (#561)
* Rewrite RenameAccountFragment to compose

* Add padding to text field, disable RENAME button when old name = new name

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-02-12 11:40:27 +01:00
Ricki Hirner
ba084255c0 Fetch translations from Transifex 2024-02-11 20:47:26 +01:00
Ricki Hirner
847d452884 Bump version to 4.3.13 2024-02-11 20:46:21 +01:00
Ricki Hirner
b2d67a5dfb Version bump to 4.3.13-rc.1 2024-02-07 21:08:01 +01:00
Ricki Hirner
94226aac1f Update dependencies, CI actions, test emulator API level 2024-02-07 11:01:34 +01:00
Ricki Hirner
8ce6fbe776 Update vcard4android (don't write structured name when FN = ORG) 2024-02-06 21:10:38 +01:00
Ricki Hirner
55c499fbe9 prepareForUpload: use UID from data class instead of querying it explicitly from content provider (#555) 2024-02-06 14:22:29 +01:00
Ricki Hirner
53a446bcf9 [CI] Update Github actions 2024-02-02 13:50:18 +01:00
Ricki Hirner
4ee6bfe276 Update dependencies (including ical4android) 2024-02-01 19:53:04 +01:00
Ricki Hirner
6817c17686 Nextcloud Login Flow: use Saved State for low memory conditions (#553) 2024-01-31 17:12:41 +01:00
Ricki Hirner
d108ea8a7b Use ksp (instead of kapt) for Hilt (bitfireAT/davx5#521) 2024-01-31 14:03:56 +01:00
Ricki Hirner
d2de737857 Version bump to 4.3.13-beta.2 2024-01-31 13:40:27 +01:00
Ricki Hirner
37e0605f9e Fetch translations from Transifex 2024-01-31 13:17:59 +01:00
Ricki Hirner
95d098541f Lint (#551)
* Lint

* Optimize imports

* Icon
2024-01-31 13:09:15 +01:00
Ricki Hirner
b2d06a491d Fix Nextcloud Login Flow "IllegalStateException: OffsetMapping.originalToTransformed" (#550)
* Move State out of Composable

* Disable text field instead of making it read-only during progress
2024-01-31 12:46:08 +01:00
Ricki Hirner
09b15c1e75 Android 14 Compatibility (#545)
* Increase target SDK level to 34

* ForegroundService: specify "data sync" foreground service type
2024-01-31 12:09:10 +01:00
Arnau Mora
a9a699c5b9 Moved to gradle/actions/setup-gradle@v3 (#549)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2024-01-30 17:12:32 +01:00
Ricki Hirner
dd036b91fc Add battery saver warning (#542)
* Show battery saver warning in account list and debug info

* Move app warnings to model class

* Debug info: more verbose text

* Restore previous strings for sync enqueued/started
2024-01-30 15:50:03 +01:00
Ricki Hirner
fbed5c7d67 WebDAV performance optimizations (#543)
* Make ranged GET requests cancellable; reduce notification update frequency

* Include original exception as a cause in WebDAV ErrnoException

* Add KDoc for threading
2024-01-30 11:55:59 +01:00
Ricki Hirner
f0eb140777 Update dependencies (including ical4android) 2024-01-26 13:05:25 +01:00
Patrick Lang
dcba94ac70 jtxBoard: added localCollection.updateLastSync() in post-processing (bitfireAT/davx5#505)
* added localCollection.updateLastSync() in post-processing

* Update build.gradle

updated ical4android version

* Update build.gradle

* Update build.gradle

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-01-25 15:46:58 +01:00
Arnau Mora
202a91a7a4 LocalEvent: synchronize UID handling with ical4android (#541)
* Got rid of uid writes

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using new ical4android version

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed commit id

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated ical4android

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2024-01-24 19:43:38 +01:00
Ricki Hirner
a313ace66d Fetch translations from Transifex 2024-01-20 13:42:05 +01:00
Ricki Hirner
bdbab77f5e Version bump to 4.3.13-beta.1 2024-01-20 13:40:53 +01:00
Arnau Mora
88e41d1eed Update dav4jvm and other dependencies (bitfireAT/davx5#518)
* Upgrade Compose Compiler to 1.5.8

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgrade Kotlin to 1.9.22

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgrade KSP to 1.0.17

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgrade dav4jvm 93dddcd

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgrade ViewModel to 2.7.0

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Updated imports

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Updated imports

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Updated imports

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2024-01-20 12:40:38 +01:00
Sunik Kupfer
ea5fc54003 Validate email before starting google oauth flow (#535)
* Validate email before starting auth flow (see #525, fixes #534)

* Simplify expression (minor change)

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-01-18 13:11:08 +01:00
Arnau Mora
0c834a5c42 Tasks intro page: "I don't need tasks" checkbox not visible in landscape mode (#530)
* Added optional modifier

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed model argument name

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added bottom padding for bottom bar

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2024-01-16 23:11:39 +01:00
Arnau Mora
2ac81b6342 Darkened colors of accounts screen background image for dark mode (#529)
* Moved all colors to resources

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Darkened colors for night mode

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2024-01-15 09:45:46 +01:00
Ricki Hirner
46a8f0f205 Add Dependent issues workflow 2024-01-11 17:35:17 +01:00
Arnau Mora
b8f4b9af30 Migrate DebugInfoActivity to Compose (#509)
* Gave more flexibility

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Migrated to Compose

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Adjusted paddings

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added missing observer

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* CardWithImagE: add another preview with subtitle, icon and content

* Made buttons uppercase

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Adjusted spacings

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed snackbar host state

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed nullable expression

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using shareFile for zip

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Minor changes (comments/formatting)

* Switched to view instead of sharing files

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Adapted image height for landscape

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* CardView: allow to pass image alignment; use card_theme_max_height

* DebugInfoActivity: paddings, images

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-01-11 17:31:08 +01:00
Ricki Hirner
3c4601f7a1 WebDAV memory cache: store values using SoftReference (bitfireAT/davx5#515)
so that the values can be free if there's not enough memory
2024-01-11 13:48:18 +01:00
Ricki Hirner
357cf09be7 WebDAV: refactor paging and caches (bitfireAT/davx5#502)
* WebDAV: don't try to load 0-byte segments

* Extracted segmentation logic to SegmentedReader for testability

* Reorganize and simplify caches

* Refactor thumbnail cache

* Use coroutines with Dispatchers.IO instead of custom Executor

* Remove obsolete classes

* Fix tests, simplify DiskCache

* Paging reader: cache current page

* PagingReader tests

* Thumbnails: timeout for generation and not only for waiting

* openDocumentThumbnail: actually cancel HTTP request when method is cancelled

* Better KDoc

* Add further tests
2024-01-10 14:59:57 +01:00
darealdemayo
6ce0d35e6d Add purelymail.com (#521) 2024-01-10 14:28:52 +01:00
Sunik Kupfer
24401cc990 Fix ProGuard issue with javax.xml.namespace.QName; update AGP (bitfireAT/davx5#512)
* Update to AGP 8.2.1
* Add keep rule for javax.xml.namespace.QName

See #511
Closes bitfireAT/davx5#499
2024-01-08 15:00:19 +01:00
Ricki Hirner
cf0c3040fc Run uploadDirty() regardless of processLocallyDeleted() result (bitfireAT/davx5#510)
Run uploadDirty() regardless of whether processLocallyDeleted() did something or not
2024-01-05 11:16:50 +01:00
Arnau Mora
6b660c2be6 Rename LoginCredentialsFragmentFactory to LoginFragmentFactory (bitfireAT/davx5#507)
* Renamed LoginCredentialsFragmentFactory to LoginFragmentFactory

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed fragment name

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Rename LoginInitFragment (managed) to ManagedLoginInitFragment

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-01-05 11:16:47 +01:00
Sunik Kupfer
20652b9325 NextcloudLoginFlowFragment: minor changes (sync bitfireAT/davx5#501)
---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2024-01-03 11:25:32 +01:00
Ricki Hirner
e5c73bead5 Downgrade AGP to 8.1.4 (fixes bitfireAT/davx5-ose#511) 2023-12-25 15:51:25 +01:00
Ricki Hirner
581622272c Bump version to 4.3.12.1 2023-12-25 15:51:18 +01:00
Ricki Hirner
320fe1dfd8 Compile releases without build and configuration cache 2023-12-25 15:50:54 +01:00
Arnau Mora
09253a2454 Upgrade Compose Compiler to 1.5.7 (#510)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-12-25 11:48:09 +01:00
Ricki Hirner
895f0a1541 Fetch translations from Transifex 2023-12-23 12:40:16 +01:00
Ricki Hirner
05360e818a Version bump to 4.3.12 2023-12-23 12:34:06 +01:00
Ricki Hirner
5d7dea0ebf Nextcloud Login flow: provide default DAV_PATH (/remote.php/dav) (#497) 2023-12-23 12:34:00 +01:00
Ricki Hirner
88238a2406 ConcurrentUtilsTest: fix testRunSingle_SameKey_Parallel (bitfireAT/davx5#494)
ConcurrentUtilsTest: changed testRunSingle_SameKey_Parallel to testRunSingle_SameKey_Nested
2023-12-20 14:34:53 +01:00
Ricki Hirner
357275fe83 Version bump to 4.3.12-rc.1 2023-12-20 14:02:08 +01:00
Ricki Hirner
e2ac368dbc Update dependencies 2023-12-20 14:02:08 +01:00
Sunik Kupfer
532a143cc8 Add PasswordTextField Composable (taken from bitfireAT/davx5#437)
---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-12-20 13:55:13 +01:00
Arnau Mora
c087834452 Rewrite TasksFragment to Compose (#481)
* Added `CardWithImage`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added `RadioWithSwitch`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Migrating to Compose

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added observers

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed functions signature

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added kdoc

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed layout

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Color for disabled

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added "don't show" behaviour

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added all tasks providers

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved checkbox to correct location

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed don't need behaviour

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added theme

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added todo

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added support for link annotations

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added support for annotated strings and urls

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added tests for HTML annotation

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Extracted `linkStyle`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed observers for requested

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed more observers

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added multiple links test

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved `installApp` to `TasksCard`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved all model calls to composable

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed preview since not usable

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Got rid of TasksFragment

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed import

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Switched link color to orange

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added missing copyright information

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Use HtmlCompat and existing Spanned.toAnnotatedString

* Added default content

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Renamed image content description

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of empty content

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Made summary of RadioWithSwitch composable

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added missing entry point annotation

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added click handling for tasks org

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of the preview provider

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Minor changes

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-12-15 13:09:42 +01:00
Ricki Hirner
da5b765b3a WebdavMountsActivity: use ShareCompat.IntentBuilder (bitfireAT/davx5#490)
WebdavMountsActivity: use ShareCompat.IntentBuilder to create sharing intent
2023-12-15 12:23:00 +01:00
Ricki Hirner
a937442a82 Update ical4android (updates ical4j to 3.2.14 and hopefully fixes bitfireAT/davx5#398) (bitfireAT/davx5#489) 2023-12-12 14:52:36 +01:00
Ricki Hirner
c56461ea9e WebDAV: allow other MIME types for (Ranged) GET, use coroutines for streaming (#503)
* StreamingFileDescriptor: use coroutines instead of threading

* WebDAV GET: accept any MIME type, but prefer known one
2023-12-12 14:39:43 +01:00
Ricki Hirner
769147b193 Use dav4jvm version that alway appends a trailing slash to MKCOL calls (#504) 2023-12-11 15:34:01 +01:00
Arnau Mora
8a0e1151ec Rewrite ExceptionInfoFragment into Compose (#485)
* Migrated dialog to Compose

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added extended compose icons

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-12-09 12:24:36 +01:00
Ricki Hirner
f2f40049b8 Update Kotlin, dependencies, Gradle 2023-12-09 11:56:00 +01:00
Ricki Hirner
7a259383be Update dependencies, GoogleLoginFragment: use clickableText instead of legacy TextView 2023-12-06 13:14:32 +01:00
Sunik Kupfer
c3e980cabf Sync add-account button code with bitfireAT/davx5#481
---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-12-05 16:07:01 +01:00
Ricki Hirner
d6861db8a6 Comment out testRefreshCollections_enqueuesWorker (bitfireAT/davx5#487) 2023-12-05 16:03:53 +01:00
Ricki Hirner
1854822757 RefreshCollectionsWorkerTest: wait for completion of enqueue (bitfireAT/davx5#486)
* RefreshCollectionsWorkerTest: wait for completion of enqueue

* Work may have finished already
2023-12-04 18:19:21 +01:00
Sunik Kupfer
787e1a687f Set METHOD_ALERT on default event reminders (#493) 2023-12-03 21:12:56 +01:00
Ricki Hirner
f8b1cd5b3c Correctly save app/build and configuration cache for dev branch builds 2023-12-03 16:59:11 +01:00
Ricki Hirner
fb7658cfaa AboutLibraries: explicitly export library definitions at release (#485)
Export library definitions for release
2023-12-03 16:20:35 +01:00
Ricki Hirner
3eb8eba70c Update AGP and dependencies 2023-12-03 16:15:57 +01:00
Ricki Hirner
fca6b5b890 Fetch translations from Transifex 2023-11-20 14:30:05 +01:00
Ricki Hirner
93cad92876 Version bump to 4.3.11 2023-11-20 14:29:15 +01:00
Ricki Hirner
8ddd3d66f0 Version bump to 4.3.11-alpha.2, CI: hopefully speed up release workflow 2023-11-20 14:28:41 +01:00
Ricki Hirner
a27a4fc7ae vcard4android: sync birthdays with time offset (#488) 2023-11-18 15:05:29 +01:00
Ricki Hirner
127511576b Update AGP to 8.1.4 2023-11-18 14:56:53 +01:00
Ricki Hirner
90c72ec013 Cache app/build to speed up builds 2023-11-17 16:12:05 +01:00
Ricki Hirner
ee637e4f6d ForegroundService: don't use stopSelf or stopForeground (bitfireAT/davx5#462)
May fix #342
2023-11-16 13:21:34 +01:00
Michael Biebl
769825b402 AboutActivity: Add back no warranty disclaimer (#477)
This was dropped during the Compose rewrite.
Also align the copyright information as it looks better.
2023-11-15 17:39:41 +01:00
Ricki Hirner
a792d4db98 Version bump to 4.3.10 2023-11-14 13:11:48 +01:00
Ricki Hirner
ef723aa555 Fetch translations from Transifex 2023-11-14 13:11:36 +01:00
Ricki Hirner
f7fc82b801 Don't allow app name translation 2023-11-14 11:59:44 +01:00
Arnau Mora
59dc681fe4 AccountsActivity: disable sync button when there are no accounts (#473)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-11-14 10:49:02 +01:00
Ricki Hirner
d22da0d230 Fetch translations from Transifex 2023-11-13 16:32:45 +01:00
Ricki Hirner
6829b3259f Bump version to 4.3.10-rc.1 2023-11-13 16:31:01 +01:00
Ricki Hirner
2f761facc9 AccountSettings: don't throw IllegalArgumentException when reading/writing sync interval (bitfireAT/davx5#456) 2023-11-13 15:07:51 +01:00
Ricki Hirner
eeffbdcf6d Go back to dnsjava 2.1.9 to avoid crashes on Android 7 (bitfireAT/davx5#454) 2023-11-13 14:32:34 +01:00
Ricki Hirner
2a9c27d4f7 AboutActivity: remove pager padding 2023-11-13 14:32:32 +01:00
Ricki Hirner
72f0579f41 Update dependencies 2023-11-13 14:32:29 +01:00
Ricki Hirner
449e886d49 AccountsActivity: center background image (fixes bitfireAT/davx5#434) 2023-11-13 10:30:46 +01:00
Arnau Mora
6055749e42 Fixed horizontal scroll (bitfireAT/davx5#447)
* Fixed sizing

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Small changes

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-11-12 11:34:50 +01:00
Ricki Hirner
0c1e4fd3cb Version bump to 4.3.10-beta.1 2023-11-09 17:02:32 +01:00
Ricki Hirner
61c1ef8831 HTTP Workers: use runInterruptible instead of interrupting manually (bitfireAT/davx5#444)
* RefreshCollectionsWorker: use runInterruptible instead of interrupting manually

* SyncWorker: use CoroutineWorker + runInterruptible

* Use global SyncWorkDispatcher that guarantees classLoader to be set

* Set SyncWorkDispatcher for whole SyncWorker's doWork

* Remove obsolete test

* SyncManager: add structured concurrency again

* Use up to <number of processors> threads for synchronization

---------

Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2023-11-09 17:02:27 +01:00
Ricki Hirner
42bd1e8449 Don't cancel service detection when DetectConfigurationFragment's view model is cleared (bitfireAT/davx5#442)
- Rewrite DetectConfigurationFragment to Compose
- Use coroutines and runInterruptible instead of Thread
- Only cancel service detection when back is pressed
2023-11-09 17:02:14 +01:00
Ricki Hirner
46615a8337 Version bump to 4.3.10-alpha.1 2023-11-09 17:02:12 +01:00
Ricki Hirner
518e5147fe Rewrite AccountsActivity to Compose (bitfireAT/davx5#431)
---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2023-11-07 17:22:44 +01:00
Ricki Hirner
9d739dd087 Clean up Hilt modules 2023-11-07 15:03:07 +01:00
Ricki Hirner
4a200dfbb7 Rewrite About activity to Compose (bitfireAT/davx5#432) 2023-11-07 14:52:42 +01:00
Ricki Hirner
cf9340107f RandomAccessCallback.Wrapper: support multiple state machine instances at same time (bitfireAT/davx5#428)
* RandomAccessCallback.Wrapper: support multiple state machine instances at same time

- support multiple state machine instances at same time
- provide explicit Exception/error code when the remote server doesn't support ranged requests

* Only use RandomAccessCallback when server explicitly advertises range requests

---------

Co-authored-by: Arnau Mora <arnyminerz@proton.me>
2023-11-07 14:11:22 +01:00
Sunik Kupfer
fbe0c4451b Add UiUtils method to render Spannables (from HtmlCompat) with Compose (bitfireAT/davx5#430)
* Add Spanned.toAnnotatedString, minor changes

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-11-01 12:00:14 +01:00
Arnau Mora
ca26155eed jtxBoard collections: honor "Manage calendar colors" account setting (bitfireAT/davx5#427)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-10-31 16:11:55 +01:00
Ricki Hirner
1a36ee2d60 Fetch translations from Transifex 2023-10-26 13:15:04 +02:00
Ricki Hirner
046bacff3f Version bump to 4.3.9 2023-10-26 12:26:17 +02:00
Ricki Hirner
73475640f7 Google Login: minor UI improvements (bitfireAT/davx5#416)
- automatically append @gmail.com
- show Go IME action for login and client ID
2023-10-26 12:26:13 +02:00
Sunik Kupfer
1e6a457a0d Fix related google calendars not being found (bitfireAT/davx5#409)
* Minor changes
- update kdoc
- rename method and variables

* Add proxy parents to related resource detection

* Rename argument, query ResourceType

* Remove unnecessary utility method

* Change parentOf to extension function; Always return URL with trailing slash

* Use calendar-proxy-read/write ResourceType from new dav4jvm

* Use max. two levels of recursion to detect shared Google calendars

* Revise test and adapt method

* Simplify HttpUrl.parent()

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-10-26 12:26:09 +02:00
Ricki Hirner
0215e98326 Version bump to 4.3.9-beta.2 2023-10-24 21:22:26 +02:00
Ricki Hirner
9dd8290004 Debug info: show periodicity and next run of sync workers (bitfireAT/davx5#415) 2023-10-24 21:22:22 +02:00
Ricki Hirner
8263b5fcf8 WorkManager: add stop reason to debug info and sync logs (bitfireAT/davx5#413) 2023-10-23 16:58:52 +02:00
Arnau Mora
fe679da03b Refactor HiltViewModels ApplicationContext (#446)
* Simplified ViewModels

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed injection

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed settings injection

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added missing import

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed application

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed constructors and got rid of utils

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Optimized imports

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added missing annotation

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-10-19 17:36:11 +02:00
Ricki Hirner
f8330e8f52 dnsjava: fix R8 rules 2023-10-18 15:31:01 +02:00
Ricki Hirner
c451c3fd70 Version bump to 4.3.9-beta.1 2023-10-18 15:20:35 +02:00
Ricki Hirner
e41ac428c9 WebcalFragment: remove unused menu item code 2023-10-18 15:19:23 +02:00
Ricki Hirner
b26ae345cd Nextcloud: pre-select contact group method (CATEGORIES) (bitfireAT/davx5#410)
* LoginActivity: refactor menu to MenuProvider; LoginModel: add contact group type

* Take LoginModel group method into account when creating the account; Nextcloud login: set preferred contact group type
2023-10-18 15:18:46 +02:00
Ricki Hirner
52747e632f LoginActivity: add Nextcloud Login Flow (bitfireAT/davx5#403)
* Replace onActivityResult by contract

* Add Nextcloud option to default login screen

* Decouple NextcloudLoginFlowComposable from model

* UI and model changes

* Single-line URL field

* Add progress indicator and other secondary UI
2023-10-18 15:17:53 +02:00
Ricki Hirner
58d4a9f663 Make all IntroFragments appear at first start (#452)
* IntroFragments: use (factory,order) List instead of (order,factory) Map to store them

* Adapt OpenSourceFragment order
2023-10-17 18:50:42 +02:00
Ricki Hirner
8ffed42eb9 PermissionsIntroFragment: take jtx Board and tasks.org permissions into account (#450) 2023-10-17 10:17:17 +02:00
Arnau Mora
5ae70cb5d0 BatteryOptimizationIntroFragment: use contract instead of onActivityResult (#444)
* Using result launcher

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Minor re-ordering

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-10-16 15:31:17 +02:00
Arnau Mora
599c905610 Replace deprecated menu overrides (#443)
* Migrated to menu provider

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed override

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Cleanup

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed menus

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Minor changes

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-10-16 15:30:26 +02:00
Ricki Hirner
c8cd6d780c [WebDAV] Add timeout for RandomAccessCallback notification (bitfireAT/davx5#408)
* [WIP] Add timeout for RandomAccessCallback

* Use state machine to handle timeout

* Use sealed class for states, guard callback access with correct states
2023-10-16 11:10:46 +02:00
Arnau Mora
4ce6fcbf44 Replaced all onBackPressed usages (bitfireAT/davx5#406)
* Replaced all `onBackPressed` usages

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added missing finish

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added more finish statements

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-10-10 18:43:44 +02:00
Sunik Kupfer
3da48ab3a2 362 increase minimum api level to android 7 (bitfireAT/davx5#363)
* Increase minSdkVersion to 24 (Android 7.0)

* Remove obsolete api level checks

* Use latest dnsjava

* Use latest apache commons

* Minor formatting

* Unify getSystemService() calls

* Remove further unnecessary calls

* Remove noinspection GradleDependency for Apache Commons libs

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-10-10 18:43:24 +02:00
Arnau Mora
0ba00d7bb0 Migrated startActivityForResult (bitfireAT/davx5#407)
Migrated activity result

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-10-10 18:42:20 +02:00
Sunik Kupfer
6d30ef42e4 Foreground service: startForeground() within 5 seconds to avoid exception (bitfireAT/davx5#405)
* Call startForeground in onCreate

* Add stopSelf() and comments

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-10-10 10:18:22 +02:00
Ricki Hirner
088136ded7 Version bump to 4.3.8 2023-10-09 12:58:09 +02:00
Ricki Hirner
caf04c4c45 Fetch translations from Transifex 2023-10-09 12:40:35 +02:00
Sunik Kupfer
81fbe2b7a6 Use worker names as work tags (bitfireAT/davx5#404) 2023-10-09 12:38:43 +02:00
Ricki Hirner
437fb30a94 Rename java sources directories to kotlin 2023-10-07 10:22:08 +02:00
Ricki Hirner
e85727e869 Update dependencies 2023-10-07 10:18:53 +02:00
Ricki Hirner
f57cd77ced Update dependencies, bump version to 4.3.8-alpha.1 2023-10-06 12:58:46 +02:00
Sunik Kupfer
6bbdcb332f Include address book account syncs, when querying sync status (bitfireAT/davx5#378)
* Use tags instead of uniqueWorkNames for work queries.

* Also include address book accounts, when querying sync status.

Give address book account sync workers their parent (main account) sync workers tag too, such that they will be included at the query for sync status of their parent account.

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-10-06 12:52:42 +02:00
Arnau Mora
6db1473e00 Update ical4android (#425)
Updates ical4j to 3.2.13, which should fix the backslash issue
2023-09-27 13:55:47 +02:00
Ricki Hirner
fd4872adb5 Minor connectivity/VPN UI adaptions (bitfireAT/davx5#391)
* Low storage: don't show notification anymore, adapt info message

- The impact of low storage is not as critical anymore that a notification is required.
- Info message adapted

* Update "account settings: ignore VPN" strings

* Update "No internet" string
2023-09-27 13:54:48 +02:00
Ricki Hirner
ba310762f9 Repair beta feedback (bitfireAT/davx5#394)
- use Google Play In-App Review API for private feedback on -gplay (fall back to email)
- start email intent again when "beta feedback" is selected in navigation drawer
2023-09-27 13:54:41 +02:00
Ricki Hirner
02c401b4d5 AccountActivity: animate progress bar becoming invisible (bitfireAT/davx5#366)
* [WIP] Animate invisibility

* Reordered for cleaner look

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Fixed animation

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Reduced animation time for going to visible to 0

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Remove logging

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Arnau Mora <arnyminerz@proton.me>
2023-09-22 17:38:27 +02:00
Arnau Mora
48327e44f5 AccountActivity: make content underneath FABs accessible (bitfireAT/davx5#390)
* Doubled bottom padding

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Account activity: increase margin for two FABs even more

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-09-22 17:38:21 +02:00
Ricki Hirner
4faecc653f Fetch translations from Transifex 2023-09-22 15:57:01 +02:00
Ricki Hirner
7be6e0636b Bump version to 4.3.7 2023-09-22 15:53:00 +02:00
Ricki Hirner
b8e4ba62f5 Update dependencies, bump version to 4.3.7-rc.1 2023-09-21 18:53:36 +02:00
Arnau Mora
ead6293be6 cert4android: certificates are not saved across app restarts; other bug fixes (bitfireAT/davx5#392)
* Updated cert4android dependency

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-09-21 18:53:27 +02:00
Arnau Mora
651ab9c53c Fixed empty translators.json (#401)
* Update translators.json

* Added JSON parsing try-catch

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Log complete exception

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-09-12 13:03:46 +02:00
Ricki Hirner
bc7a320916 Don't start multiple tests for PRs 2023-09-12 13:03:15 +02:00
Sunik Kupfer
464ba7d76e Tests for ConnectionUtils (bitfireAT/davx5#372)
* Add tests for internetAvailable()

* Add tests for wifiAvailable()

* Add TODO for test case

* Add tests for internetAvailable() covering multiple network connections

* Minor KDoc

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-09-11 11:41:41 +02:00
Angus Gratton
52e26e34d5 DavDocumentsProvider: Log a warning for responses of type OTHER. (#400)
If a WebDAV server is misconfigured (for example, behind an HTTP proxy that
strips a URL prefix and doesn't correctly forward this to the WebDAV server)
then PROPFIND can return resources whose URLs don't match the request URL. These
are resolved by dav4jvm as HrefRelation.OTHER.

Currently this situation produces no output at all in DAVx5 (logs or app) and
the WebDAV share appears accessible but empty. It is possible to create files in
the share, but not to see them again afterwards!

Of course a misconfigured server isn't the WebDAV client's responsibility to
resolve, but adding a warning in the log provides an extra clue for anyone
trying to debug it.
2023-09-08 13:48:39 +02:00
Ricki Hirner
0c991288d5 Bump version to 4.3.6.1 2023-09-07 00:29:39 +02:00
Ricki Hirner
f1a1d0efd8 Fix bug where sync isn't possible as soon there's any connection without INTERNET (bitfireAT/davx5#370)
Fix bug where sync wasn't possible as soon there's any connection without INTERNET (bitfireAT/davx5#369)
2023-09-07 00:29:23 +02:00
Ricki Hirner
56deab70ee Version bump to 4.3.6 2023-09-06 16:50:09 +02:00
Sunik Kupfer
5640250359 Wait an appropriate delay before SyncWorker retries after soft errors (bitfireAT/davx5#337)
* Use appropriate delayUntil value for retrying syncs on 503s.

Crop server suggested retryAfter value to self defined min/max values and use a reasonable default value if non-existent.

* Add tests for getDelayUntil

* Wait appropriate delay, before retrying sync after a soft error happened

* Increase max and default sync delays after soft errors

* Increase initial backoff time for SyncWorker retries

* Minor getDelayUntil changes

* Minor changes

- store delayUntil in seconds
- pass duration instead of timestamp to Thread.sleep
- other minor changes

* Use Instant instead of Long timestamps

* Correct calculation of blocking duration

* Indicate soft error occurred on 503 server message

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-09-05 20:30:52 +02:00
Ricki Hirner
b670979f12 Fetch translations from Transifex 2023-09-05 20:30:30 +02:00
Ricki Hirner
251cf1b2e9 Update dependencies (including vcard4android), bump version to 4.3.6-rc.1 2023-09-05 10:42:33 +02:00
Ricki Hirner
4ad54cd28b Update dependencies (including cert4android and vcard4android) (bitfireAT/davx5#360)
* Update dependencies (including cert4android and vcard4android)

* Migrated to new version of ical4android

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Increased compileSdk

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgraded browser

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Only use `appInForeground` for `customCertsUi`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed unnecessary variable and fixed trust manager

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Cleaned up trust manager factory

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Update dependencies (including cert4android and vcard4android)

* Migrated to new version of ical4android

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Increased compileSdk

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgraded browser

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Only use `appInForeground` for `customCertsUi`

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed unnecessary variable and fixed trust manager

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Cleaned up trust manager factory

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Minor changes

* Fixed build for SDK 34

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Migrated certificate trusting

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* NetworkConfigProvider: handle invalid trusted certificate

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Arnau Mora <arnyminerz@proton.me>
2023-09-05 10:41:54 +02:00
Sunik Kupfer
1c419cd75c Add setting to ignore VPNs at connection detection (bitfireAT/davx5#356)
* Add setting to ignore VPNs at connection detection

* Minor changes

- move methods to ConnectionUtils to keep SyncWorker class compact
- always use "ignore VPNs" as Boolean
- other minor changes

* Show ignore VPNs setting only below api lvl 23

* Change strings

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-09-05 10:38:38 +02:00
Arnau Mora
62cca2939a Upgrade AppIntro to 7.0.0-beta02 (bitfireAT/davx5#357)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-09-05 10:37:16 +02:00
Arnau Mora
795ae49da4 SyncWorker "soft error (max retries reached)" notification confusing for users (bitfireAT/davx5#353)
* Sync error notification dismiss

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* FIXME

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added click intent

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Delayed error info

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added tag for max retries

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Reduced priority

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed max retries tag

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Using account name as tag

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added authority to notification tag

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added account type to notification tag

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed priority to min

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-09-05 10:37:08 +02:00
Arnau Mora
bd7b2714d2 Upgrade AGP to 8.1.1 (bitfireAT/davx5#355)
* Upgrade AGP to 8.1.1

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Upgrade Kotlin and dependencies

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Downgrade browser

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-09-05 10:37:00 +02:00
Ricki Hirner
273deecbe4 Update dependencies 2023-08-16 22:59:20 +02:00
Arnau Mora
a375a16edf Added tooltip to sync collections FAB (bitfireAT/davx5#340)
* Added tooltip to sync collections fab

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Greyscale tint for collections sync FAB

* Always use TooltipCompat for FAB tooltips

Closes bitfireAT/davx5#339

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-08-15 18:23:27 +02:00
Sunik Kupfer
4ce33e949b Use AccountManager.setAndVerifyUserData extension method, instead of setUserData (bitfireAT/davx5#344)
Hopefully fixes bitfireAT/davx5#308

* Add new setAndVerifyUserData extension function to AccountManager
* Use new setAndVerifyUserData extension function instead of insecure setUserData
* Update KDoc [skip CI]

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-08-15 18:23:00 +02:00
Ricki Hirner
fcb9d7f560 Fetch translations from Transifex 2023-08-07 20:45:55 +02:00
Ricki Hirner
3709c2ab32 Version bump to 4.3.5.2 2023-08-07 20:44:00 +02:00
Ricki Hirner
8aeb2d5064 Fix onPerformSync crash on InterruptedException (bitfireAT/davx5#343)
- use Kotlin and CompletableDeferred instead of Java synchronization
- signal cancellation by completing CompletableDeferred instead of Thread.currentThread.interrupt()
2023-08-07 20:43:51 +02:00
Arnau Mora
f806122b00 Upgrade AGP to 8.1 and configure per app language (bitfireAT/davx5#338)
* Upgraded AGP

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Enabled automatic locale config generation

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added fallback language

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added legacy service

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added `Accept-Language` header to custom tabs

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Nextcloud Login Flow/Google OAuth: also send language tag for default locale

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-07-28 10:53:57 +02:00
Ricki Hirner
4f192c253d Use KSP for Room (bitfireAT/davx5#333) 2023-07-27 18:20:44 +02:00
Ricki Hirner
7038bbf70a Google OAuth: fix manifest merging so that intent-filter for OAuth callback is included again (#367) 2023-07-27 18:13:57 +02:00
Ricki Hirner
b7e00fac40 Fetch translations from Transifex 2023-07-22 12:23:44 +02:00
Ricki Hirner
13d9ce089b Bump version to 4.3.5.1 2023-07-22 12:22:28 +02:00
Ricki Hirner
d74e8da5c1 Update dependencies 2023-07-22 12:22:26 +02:00
Arnau Mora
a2b633edc0 Updated Google login button (bitfireAT/davx5#332)
* Updated Google login button

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Moved Google G logo file

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed background color

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Amend warning sign [skip CI]

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-07-22 12:22:22 +02:00
Ricki Hirner
e6125dd644 Update README, add Mastodon button [skip CI] 2023-07-16 12:14:48 +02:00
Ricki Hirner
162c9effe0 Update to latest dav4jvm (uses Java 8 Time API) (bitfireAT/davx5#329)
Fixes WebDAV file timestamps always being null (see bitfireAT/dav4jvm#22)
2023-07-14 23:11:44 +02:00
Arnau Mora
c62874a34b Make "Refresh collection list" more visible (bitfireAT/davx5#266)
* Added refresh collections fab

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added listener for clicks on refresh collections fab

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Adjusted sizing

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated tooltip and description for collections sync

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed Snackbar

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added warning for null serviceId

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed refresh collections service id fetching method

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Tooltip updates on refresh collections list

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Migrate to ViewPager2; show "Refresh collections" for WebCal, too

* Added refresh collections fab

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added listener for clicks on refresh collections fab

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Adjusted sizing

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated tooltip and description for collections sync

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed Snackbar

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added warning for null serviceId

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Changed refresh collections service id fetching method

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Tooltip updates on refresh collections list

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Migrate to ViewPager2; show "Refresh collections" for WebCal, too

* Changed collections refresh action update method

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Use lambda syntax for observers

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2023-07-14 23:11:32 +02:00
Ricki Hirner
36e377001f Clean up unused resources
- also fixes some wrong resources
2023-07-11 11:47:16 +02:00
Ricki Hirner
400d46fce2 Amend comments for repository identity 2023-07-10 15:22:24 +02:00
Ricki Hirner
ec34da1ace Google Login: add Limited Use policy (bitfireAT/davx5#327) 2023-07-10 15:14:21 +02:00
Ricki Hirner
bb6ed7ec64 Fetch translations from Transifex 2023-07-10 15:13:23 +02:00
Ricki Hirner
042b2371fb Version bump to 4.3.5 2023-07-10 15:12:04 +02:00
Ricki Hirner
5272943a05 Google: use new API base URL (bitfireAT/davx5#324)
Google: use new API base URL (finds secondary calendars and homesets)
2023-07-07 11:30:22 +02:00
Sunik Kupfer
318939e970 Ensure internet connection before syncing (bitfireAT/davx5#305)
* Comment out failing test

* Check internet connection before syncing for API 23+

* [Skip CI] Amend comments

* Comment out whole test class

* Comment out whole test class

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-07-06 23:45:08 +02:00
Ricki Hirner
1478306e54 Comment out failing test 2023-07-06 23:45:06 +02:00
Sunik Kupfer
c7ff42c03f [Google OAuth] fix Re-authentication crash (bitfireAT/davx5#319)
* Hide/Show auth settings instead of removing them prevents NPE

* Provide original OAuth login email as default value for GoogleLoginFragment started from account settings

* Account settings: explicitly trim empty user name to null

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-07-06 23:45:01 +02:00
Ricki Hirner
17ccb305f9 Configuration detection: allow null Credentials (anonymous login) (bitfireAT/davx5#323) 2023-07-06 23:44:11 +02:00
Sunik Kupfer
37a7ebb9c0 Fix test again (bitfireAT/davx5#321) 2023-07-06 23:44:08 +02:00
Sunik Kupfer
1c403d171c Fix test (bitfireAT/davx5#320) 2023-07-06 23:44:00 +02:00
Arnau Mora
81273f028e Added known base urls (bitfireAT/davx5#315)
* Added known base urls

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Change order

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-07-03 12:07:57 +02:00
Sunik Kupfer
fac68680ee Block sync adapter's onPerformSync until SyncWorker finishes (bitfireAT/davx5#278)
* Block sync framework until SyncWorker finishes

* Bump version code for 4.3.3 (previous version code was never released publicly)

* Fetch translations from Transifex

* Release internal version automatically [skip ci]

* Update periodic sync workers when "Sync only on WiFi" flag is changed (#282)

* Update periodic sync workers when "sync only on WiFi" flag is changed
* Remove BootCompletedReceiver which was only needed to repair sync intervals (not required with WorkManager anymore)

* Bump version code to 403030006 (stays 4.3.3)

* Use unique worker name, Java notify/wait and observeForever

* Remove observer when sync finished

* Catch and ignore, but log interruption exceptions

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-07-03 12:07:39 +02:00
Ricki Hirner
46488f1618 Version bump to 4.3.5-alpha.1; update Compose; move GoogleLoginFragment to fix build 2023-06-30 13:51:09 +02:00
Sunik Kupfer
0f92b0fb05 291 clean up oauth mess (bitfireAT/davx5#307)
* Move GoogleOAuth members into GoogleLoginFragment

* Require login flow capable browser and notify user if missing

* Receive AppAuth redirects only in standard and gplay flavor

* Set davx5 as user-agent for AppAuth connection builder

* Re-authentication in Account settings

* Catch unauthorized exceptions at collection refresh and notify user to re-authenticate

* Suggest email address on account creation

* Set contact groups default setting as per-contact categories for oauth logins

* Add authentication to debug info, minor other changes

* Better error handling; don't pre-set group type

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-30 12:53:16 +02:00
Ricki Hirner
6a2c366358 Update dependencies; CI: don't checkout submodules 2023-06-28 22:42:24 +02:00
Arnau Mora
4ba50b969a Migrated to ical4android library (bitfireAT/davx5#302)
* Migrated to ical4android library

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Manually excluded Groovy

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Updated ical4android

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Update dependencies

* Added more exclusions

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Also update Room [skip CI]

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-28 22:30:53 +02:00
Michael Biebl
473dca62fa Shorten version hash of vcard4android library (#345)
Looks better in AboutLibraries and is more consistent with cert4android
and dav4jvm.
2023-06-28 12:53:43 +02:00
Arnau Mora
11c8866614 Security: check/fix DebugInfoActivity: Uncontrolled data used in path expression (bitfireAT/davx5#267)
* Passing file name to `EXTRA_LOG_FILE` instead of path. Replaces illegal chars

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed log file extra

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Removed log file extra

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added more error logging

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Update KDoc, remove strings

* Moved ViewModel initialization to Factory

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed ViewModel factory

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Updated annotations

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Handle EXTRA_LOGS correctly

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-28 12:52:57 +02:00
Arnau Mora
3ba463dbd4 Removed Twitter from navigation drawer (bitfireAT/davx5#312)
Signed-off-by: Arnau Mora <arnyminerz@proton.me>
2023-06-26 08:53:19 +02:00
Arnau Mora
837fcc7805 Removed Twitter links (#351) 2023-06-25 14:11:24 +02:00
Arnau Mora
f820e92d15 Integrate vcard4android as an android library (bitfireAT/davx5#300)
* Migrated to external dependency for vcard4android

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Removed vcard4android submodule

* Update vcard4android version, remove from settings.gradle

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-14 14:21:10 +02:00
Ricki Hirner
1b521e3c17 Bump version code 2023-06-13 21:01:10 +02:00
Ricki Hirner
50cb223bb6 DavResourceFinder: make input arguments explicit (bitfireAT/davx5#298) 2023-06-13 21:00:56 +02:00
Ricki Hirner
764c382af3 Fetch translations from Transifex 2023-06-13 18:47:08 +02:00
Ricki Hirner
aed721409f Bump version to 4.3.4.1 2023-06-13 18:43:06 +02:00
Ricki Hirner
24a3954d75 Google OAuth: fix endless loop in Fragment (bitfireAT/davx5#295)
Fix endless loop
2023-06-13 18:40:49 +02:00
Sunik Kupfer
ba36d01e11 [Google OAuth] Support custom Client IDs (bitfireAT/davx5#294)
* Support custom Client IDs

* Refactoring

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-13 18:39:40 +02:00
Ricki Hirner
ff4af11bcf Minor HttpClient change 2023-06-11 14:30:48 +02:00
Ricki Hirner
66c93414c0 Fetch translations from Transifex 2023-06-11 14:30:24 +02:00
Ricki Hirner
278171dbac Bump version to 4.3.4 2023-06-11 14:29:28 +02:00
Ricki Hirner
5b38943205 Support OAuth for Google (bitfireAT/davx5#289)
* Proof of concept (without auth state storage)
* Implement Bearer authentication, create network package
* Properly create/dispose AuthService
* Use proper ActivityResultContract
* Integrate into default login activity
* Change client ID to davx5integration@gmail.com
* Google Login: adapt login view
* Fix tests
* Don't allow empty Google account
* Move strings to resources
2023-06-11 14:17:50 +02:00
Sunik Kupfer
1b3fde0854 280 fix problematic toasts (bitfireAT/davx5#283)
* When renaming show a toast when account with new name exists already

* Replace toasts with snackbars

* Apply code styling hints

* Move lambdas out of parentheses

* Inject application instead of context

* Replace Toast with Snackbar

* Shorten messages shown in snackbars

* Show Toast in UI instead of model; use AndroidViewModel

* Move account name check out of lock; don't close activity if not successful

* Duplicate account name check: only check for our account type

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-09 17:26:11 +02:00
Sunik Kupfer
caf7be5e11 New setting to preselect only personal collections (bitfireAT/davx5#276)
* Add new preselect_collections option and deprecate sync_all_collections option

* Optimize imports

* At refresh, decide on whether a collection should be preselected

* Add preselect_collections_blacklist setting and restriction

* Adhere to preselect_collections_blacklist setting

* Add preselect_collections values

* Also check for empty regex string and use new setting values

* Add unit tests

* Remove sync_all_collections setting and restriction

* Blacklist nextclouds recently contacted addressbook in restriction and setting by default

* Improve kdoc

* KDoc, changed setting names, minor code optimizations

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-06-06 21:03:50 +02:00
Ricki Hirner
3b2246e74b Bump version code to 403030006 (stays 4.3.3) 2023-06-01 19:12:47 +02:00
Ricki Hirner
12902a051f Update periodic sync workers when "Sync only on WiFi" flag is changed (bitfireAT/davx5#282)
* Update periodic sync workers when "sync only on WiFi" flag is changed
* Remove BootCompletedReceiver which was only needed to repair sync intervals (not required with WorkManager anymore)
2023-06-01 19:12:41 +02:00
Ricki Hirner
bed762ab7d Fetch translations from Transifex 2023-06-01 12:31:23 +02:00
Ricki Hirner
b56dd96fd8 Bump version code for 4.3.3 (previous version code was never released publicly) 2023-06-01 12:30:11 +02:00
jgirardet
cb5f722a87 Add mailo.com to known base urls (#329)
* Add mailo.com to known base urls

* fix order
2023-05-31 09:51:04 +02:00
Ricki Hirner
dceb1493eb Remove cert4android from settings.gradle, remove obsolete adb-log.sh, update ical4android 2023-05-30 17:09:37 +02:00
Arnau Mora
17718a7ed8 Integrate cert4android as an android library (bitfireAT/davx5#274)
* Removed `cert4android` submodule

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added cert4android dependency

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Update dependencies, fix invalid style

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-05-30 11:10:06 +02:00
Arnau Mora
6d278b7e04 Made collection url selectable (bitfireAT/davx5#275)
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
2023-05-23 17:02:33 +02:00
Sunik Kupfer
ae303e3e8d Refactor queryHomeSets method for clarity (bitfireAT/davx5#270)
* Make it clear that the first call of recursive method queryHomeSets is for the current-user-principal home set

* Update whitespace in kdoc

* Update kdoc
2023-05-23 17:02:26 +02:00
Ricki Hirner
38cfd200b1 Fetch translations from Transifex 2023-05-22 08:27:33 +02:00
Ricki Hirner
968db83366 Version bump to 4.3.3 2023-05-22 08:26:46 +02:00
Ricki Hirner
8923f9b1ad Fix workflow concurrency 2023-05-17 22:15:49 +02:00
Ricki Hirner
e60723042b Cancel running actions for previous commits if there are new pushes on the same branch/tag 2023-05-17 18:24:15 +02:00
Ricki Hirner
5fee2375a5 Update dependencies, bump version to 4.3.3-rc.1 2023-05-17 18:13:27 +02:00
Ricki Hirner
7f62148c75 Disable configuration cache for release builds (closes bitfireAT/davx5#263) (bitfireAT/davx5#269) 2023-05-17 18:13:27 +02:00
Sunik Kupfer
a072cf0404 Add last synced time to collection properties (bitfireAT/davx5#260)
* Add last synced time to collection properties

* Show last synced time for every to the collection relevant authority

* Try finding the application name for given package name

* Resolve authority to package name before finding application label

* Use AndroidViewModel instead of ViewModel; change model LiveData to val

* Rewrite to Compose, use relative time description

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-05-17 18:13:27 +02:00
Ricki Hirner
5aff37a9a6 Add style guide 2023-05-17 18:13:27 +02:00
Ricki Hirner
2f1757bb78 Version bump to 4.3.3-beta.2
- minor workflow changes
2023-05-17 18:13:27 +02:00
Ricki Hirner
34cc3ac670 Update ical4android (increase minimum jtx Board version) and vcard4android (fixes NoYearOfEra bug) 2023-05-17 18:13:27 +02:00
Ricki Hirner
55ee89529f Update dependencies, including cert4android and vcard4android 2023-05-17 18:07:17 +02:00
Ricki Hirner
83b5b939e1 Add issue template file so that we can enable issues for ourselves 2023-05-12 11:25:37 +02:00
Ricki Hirner
b543dc9332 Fetch translations from Transifex 2023-05-12 10:50:46 +02:00
Ricki Hirner
28466ace46 Version bump to 4.3.3-beta.1 2023-05-12 10:49:38 +02:00
Sunik Kupfer
63f51c4b42 Account settings migration 13 to 14 (bitfireAT/davx5#262)
* AccountSettings: prevent redundant calls, move migrations to separate class

* Don't create AccountSettings while migrating

* Double thread sleep time to wait for sync framework to get disabled, and update logging

* Remove manual testing helper

* Fix tests

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-05-12 10:23:47 +02:00
Ricki Hirner
8ac8971293 Update dav4jvm (WebDAV timestamps), ical4android (jtx geofence x radius), Github workflow actions 2023-05-12 00:14:05 +02:00
Arnau Mora
9748b75671 Fixed toast threading (bitfireAT/davx5#256)
* Fixed toast threading

* Added `AndroidViewModel.context`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Moved to `AndroidViewModel`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed error invocation

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added back constructor inject

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Save Application with val instead of util method

* Use LiveData for error message

---------

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-05-11 18:41:07 +02:00
Sunik Kupfer
2ebb40b152 Implement proper network check before manual sync (bitfireAT/davx5#259)
* Implement proper network check before manual sync

Do a proper connectivity check, to show a message to the user, about the sync being scheduled for when connectivity is available, before enqueueing the SyncWorker.

* Update the sync status in accounts adapter to include pending syncs

This is relevant for when a manual sync is triggered without connectivity.

* Replace Toast by Snackbar

* Replace ViewModel by AndroidViewModel

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-05-11 18:40:29 +02:00
Ricki Hirner
5cbf14fa27 Update ical4android to fix TZ with negative DST (Google Europe/Dublin) bug 2023-05-10 13:21:12 +02:00
Ricki Hirner
2ff4b35bdb Use configuration cache to improve build speed (closes bitfireAT/davx5#248) (bitfireAT/davx5#255)
- disable per-app language preference for now (will be enabled again with bitfireAT/davx5#245)
- disable Jetifier (doesn't seem to be explicitly required for in-app rating API anymore)
- remove Locator dependency
- update ical4android
2023-05-10 13:20:57 +02:00
Ricki Hirner
7b2ffeb98e Update dependencies 2023-05-09 12:36:45 +02:00
Ricki Hirner
9917f7131a Comment out AccountSettingsTest for now because they cause strange failures 2023-05-07 08:03:21 +02:00
Ricki Hirner
1c474df606 Version bump to 4.3.3-alpha.1 2023-05-07 08:03:21 +02:00
Ricki Hirner
c3cd970581 Use Jetpack Compose for AboutLibraries (bitfireAT/davx5#246)
- add JetPack Compose dependencies
- use existing XML theme as Compose MdcTheme
- update AboutLibraries to newest version (uses Compose)
- embed AboutLibraries Compose into existing Fragment layout
2023-05-07 08:03:21 +02:00
Ricki Hirner
0bed00c05a Handle InvalidAccountException in SyncAdapterService 2023-05-07 08:03:21 +02:00
Sunik Kupfer
f9d6bb153c Workmanager scheduled sync (bitfireAT/davx5#216)
Closes bitfireAT/davx5#14

* extract WorkManager util functions and kdoc
* add PeriodicSyncWorker
* refactor and some kdoc
* use PeriodicSyncWorker and add todos
* allow SyncAdapter arguments to be passed to SyncWorker and have re-synchronization use SyncWorker
* handle sync cancellation in AccountSetting migration
* handle sync cancellation when account is renamed
* remove sync frameworks global sync setting and sync state awareness for the GUI
* Observe sync worker status in AccountListFragment
* Create setup for stepwise refactor of the sync adapters. This should keep the app from failing to build.
* Create new abstract BaseSyncer class for generic sync code. Adapt consumers and tests to use the new class.
* Move calender sync code to new CalenderSyncer class which is independent from the sync framework
* In CalendarSyncAdapterService pass sync requests from Sync Adapter Framework to SyncWorker
* Use CalenderSyncer in SyncWorker
* Move contacts sync code to new ContactsSyncer class.
* Move address book sync code to new AddressBookSyncer class
* Move jtx sync code to new JtxSyncer class
* Move tasks sync code to new TaskSyncer class
* Remove duplicate code in inherited sync adapters.
* Remove refactoring helper interface, duplicate generic sync code and some linting.
* Adapt tests for new Syncer class and move to the new package
* Remove remaining duplicate code in SyncAdapterService, add todos and edit kdoc.
* Move all the single line sync adapter services into one file.
* Remove concurrent sync runner code and its test, as we now use WorkManagers one time work requests.
* Remove SAF manual sync flag usage where unnecessary.
* Drop ability to prioritise collections for sync, as not used and hindering removal of sync adapter arguments.
* Pass simple string array to SyncWorker instead of bundle to simplify code.
* Restructure work query code.
* Get debug info from account settings and WorkManager.
* Write tests for PeriodicSyncWorker
* Test account creation will set a default sync interval for CardDAV and CalDAV
* Throw an exception if accountManager returns null for sync interval value
* Do proper interval check and add tests for AccountSettings
* Use work manager query to determine whether work is in a specific state
* [WIP] Add test to check that task provider is configured correctly on account creation
* Edit test checking that task sync is configured correctly on account creation with/without installed task app(s), by mocking TaskUtil
* Edit test such that it does not require a flaky flag
* Remove periodic sync when tasks app is uninstalled
* Bring back content triggered syncs
* Rename enqueueSyncWorker method to enqueue only for clarity
* Enable SyncAdapterFramework to cancel running SyncWorker
* Add test ensuring that SyncWorker.onStopped() interrupts the running sync thread
* Add retry policy sync on soft errors
* Check users sync conditions before enqueueing SyncWorker
* Add test for whether user sync conditions are treated correctly and kdoc
* Rename ambiguous shorthand "SAF" to "SyncFramework", as SAF usually means StorageAccessFramework
* Migration: Disable sync framework periodic syncs when interval is changed for specified authority
* Add Workers info to debug info
* Use WorkInfo.runAttemptCount to fail work after 20 soft errors
* Notify user if retry limit for soft errors has been reached
* Remove left over concurrency sync run tests prevention
* Migration: Continue to remove periodic sync framework syncs until user migration to PeriodicSyncWorker syncs is complete
* Kdoc and small changes
* Migration: Change to hard migration strategy
* Drop repairSyncIntervals method in favor of hard migration strategy
* Improve debug info of workers
* Remove sync framework periodic syncs, created by enabling content triggered syncs
* Change minimum sync interval to 15 min; minor other changes
* Fix tests
* Implement requested changes and update kdoc
* Add network connectivity restrictions to PeriodicSyncWorker
* Minor changes
* Move back sync classes to syncadapter package for now (can be separated later)
* Add KDoc
* Changes from review
* Rename test methods
* Add back global sync status warning

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-05-07 08:03:21 +02:00
Ricki Hirner
299fb27af4 Fetch translations from Transifex 2023-05-02 14:06:43 +02:00
Ricki Hirner
9c51dadce8 Version bump to 4.3.2 2023-05-02 13:57:31 +02:00
Ricki Hirner
ae3f5c8bab Fix ProGuard rules for R8 (AGP 8.0) 2023-05-01 11:00:37 +02:00
Ricki Hirner
aa063adaba Upgrade gradle, AGP, dependencies 2023-04-29 22:50:18 +02:00
Sunik Kupfer
c5b02d2bde Log warning if principal URL does not provide CalDAV/CardDAV service (bitfireAT/davx5#242)
* Log warning if principal URL does not provide CalDAV/CardDAV service

* Unified log messages for identical cases

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-04-20 14:01:30 +02:00
Arnau Mora
61b6cd5e2e Disabled GPS feature requirement (bitfireAT/davx5#241)
* Disabled GPS feature requirement

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Updated GPS feature comment

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

---------

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
2023-04-18 11:11:04 +02:00
Ricki Hirner
aecabb9845 Update dependencies 2023-04-13 11:25:51 +02:00
Michael Biebl
16573e9ea6 Sync interval failure (#235)
* Fix log message

* Log a warning if repairing the sync interval failed
2023-04-12 11:41:24 +02:00
Ricki Hirner
e2bfda71bc Use large runners for CI Android tests (#306) 2023-04-12 11:39:32 +02:00
Ricki Hirner
133d155b94 Update ical4android; CI: use gradle/gradle-build-action for caching 2023-03-31 16:12:20 +02:00
Sunik Kupfer
874ad7b1c6 Handle error responses when querying principals for display names (closes bitfireAT/#237) (bitfireAT/#238)
* Add test with inaccessible principal

* Catch and log exceptions where server resource is inaccessible

* Catch and log every HttpException
2023-03-31 13:29:20 +02:00
Sunik Kupfer
fc17d0ed69 SettingsActivity resync now uses SyncWorker (bitfireAT/davx5#226)
* Alter the SyncWorker sync request to allow passing sync arguments along

* Request sync in SettingsActivity via SyncWorker instead of ContentResolver

* Don't use catch-all arguments as worker arguments

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-03-28 16:59:36 +02:00
Ricki Hirner
2f8340a646 SyncManager: fix logging string that always talked about address data even for events/tasks 2023-03-24 11:52:19 +01:00
Ricki Hirner
6050396a45 Update Transformations to new syntax (androidx.lifecycle:lifecycle-viewmodel-ktx:2.6) 2023-03-24 10:46:42 +01:00
Ricki Hirner
2503d92dca Update dependencies, including ical4android (now ignores invalid properties)
Closes bitfireAT/davx5#165
2023-03-23 17:09:37 +01:00
Ricki Hirner
983a142670 Add CodeQL security scanning (#296)
* Create .github/workflows/codeql.yml

* Build explicitly instead of using autobuild (because it doesn't work)

* Checkout submodules and cache Java dependencies
2023-03-23 11:29:29 +01:00
Arnau Mora
a3a8317b2f Fix fetching list of translators (bitfireAT/davx5#232)
* Changed `password` with `token`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Fetch script: don't use --minimum-perc 20 because it's in .tx/config

---------

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-03-21 15:46:58 +01:00
Ricki Hirner
880abf024c Fetch translations from Transifex 2023-03-20 13:10:34 +01:00
Ricki Hirner
83936b97ea Bump version to 4.3.1.1 2023-03-20 12:58:10 +01:00
Ricki Hirner
50dbd66d3a Version bump to 4.3.1 2023-03-13 18:01:20 +01:00
Arnau Mora
17fb1168cc Generate locales with Locator (bitfireAT/davx5#218)
* Added locator plugin

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Removed old generator

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Updated source sets

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added locator submodule

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Removed locator submodule

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Moving scripts

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Update dependencies

* Added `LocatorPlugin`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Fixed locales generation

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Removed buildSrc

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Using plugin from Jitpack

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Using plugin from Gradle

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Removed comment

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Remove jitpack

---------

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-03-13 18:01:13 +01:00
Ricki Hirner
89a698e7b2 Bump version to 4.3.1-beta.1 2023-03-02 13:11:09 +01:00
Ricki Hirner
6dd0af2d7a Update Kotlin and gradle plugin 2023-03-02 13:11:08 +01:00
Patrick Lang
29a97c85ff Fixed language selector icon for night mode (bitfireAT/davx5#223)
Update ic_language.xml

Replaced tint by  android:tint="?attr/colorControlNormal">
2023-03-02 13:11:01 +01:00
Sunik Kupfer
9fee782968 Check sync workers properly (closes bitfireAT/davx5#217) (bitfireAT/davx5#220)
* check sync workers properly

* Move utils around

* Merge live data, instead of observing in view, to recalculate account list.

* Update LiveData with same value

* Remove unused context property.

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-02-28 10:28:52 +01:00
Sunik Kupfer
fe7a928064 Provide owner url in jtx collection (bitfireAT/davx5#221)
* provide owner url in jtx collection

* Updated contract, added owner displayname to collections sync

* Update ical4android

---------

Co-authored-by: Patrick Lang <72232737+patrickunterwegs@users.noreply.github.com>
2023-02-28 10:28:46 +01:00
Ricki Hirner
54c6117ba2 Update dependencies 2023-02-28 10:28:29 +01:00
Arnau Mora
fffb029362 Fixed missing languages and wrong display (bitfireAT/davx5#214)
* Displaying language with `displayName` instead of `displayLanguage`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added languages from `davdroid`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Fixed wrong locale tags

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Using Map instead of list for language loading

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Typo

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Changed language loading logic

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Improved docs

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Moved method from `LangUtils`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Using regex

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added test for `resourceQualifierToLanguageTag`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

---------

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
2023-02-28 10:28:19 +01:00
Sunik Kupfer
b83d116f40 show real name of davowner (bitfireAT/davx5#208)
* add principal table with dao

* add principal table with dao

* collection saves ownerId instead of owner URL

* save and refresh principals

* show display name of collection owner in GUI

* show only the owner name (preferably) or respective url

* remove principals which do not own any collections

* Don't mock AppDatabase

* ensure we are really dealing with a principal and save it even without its display name

* ensure owner label is hidden when neither owner-displayname nor  owner-url are available

* save principal urls without trailing slash

* use a custom query to find principals without collections

* Some changes

- insertOrUpdateByUrl
- don't explicitly set id=0 when not necessary,
- make it work when there are already entries with trailing slahes
- added TODOs

* Small changes
- Update principal only if display name changed
- Rename methods
- Kdoc
- Tests

* stop using simple methods with vague names

* rename method insertOrUpdateOrGet to insertOrUpdate and leave existing kdoc for explanation

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-02-20 13:29:56 +01:00
Ricki Hirner
26961d9749 Update dependencies 2023-02-20 13:29:52 +01:00
Patrick Lang
7b34f8148f jtx-board-2-3-new-recur-handling (bitfireAT/davx5#196)
* adapted JtxSyncManager, added new method in LocalJtxCollection

* minor code improvement

* inserting recurring entry as well, as the series might not have been processed yet

* version bump (preliminarily)

* version bump (preliminarily)
2023-02-20 13:29:44 +01:00
Ricki Hirner
4dd79606f2 F-Droid changelog for 4.3 2023-02-08 14:04:40 +01:00
Ricki Hirner
f13164af41 Fetch translations from Transifex, tx config changes 2023-02-08 12:39:40 +01:00
Ricki Hirner
43b6b8b3db Version bump to 4.3 2023-02-08 12:33:16 +01:00
Ricki Hirner
d00966063e Update ical4android 2023-02-08 12:33:15 +01:00
Arnau Mora
6990479d18 Enabled automatic popup of the custom certificate dialog (bitfireAT/davx5#207)
* Enabled automatic popup of the custom certificate dialog

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Use chaining instead of .let

---------

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-02-08 12:32:29 +01:00
Ricki Hirner
1dd93ae43a Collections fragment: cache paging data (bitfireAT/davx5#206)
Closes bitfireAT/davx5#205
2023-02-07 17:14:21 +01:00
Michael Biebl
179602df46 Drop duplicated .ForegroundService (#266)
Was added by mistake in cfee0f3461.
2023-02-07 17:13:04 +01:00
Ricki Hirner
412acbcc7d Version bump to 4.3-rc.2 2023-02-03 21:08:24 +01:00
Ricki Hirner
dc207682a5 CollectionsFragment: Replace Kotlin flow by LiveData (closes bitfireAT/davx5#201) (bitfireAT/davx5#204) 2023-02-03 21:08:14 +01:00
Ricki Hirner
c8c1124303 Fix release workflow 2023-02-03 21:07:35 +01:00
Ricki Hirner
00235e12a4 Fetch translations from Transifex 2023-02-01 14:02:59 +01:00
Ricki Hirner
e283f5f34b Version bump to 4.3-rc.1 2023-02-01 13:27:56 +01:00
Ricki Hirner
2349468b0b Update dependencies, including ical4android 2023-02-01 13:27:55 +01:00
Sunik Kupfer
cf4b7891e3 fix collections vanishing and remove useless home set reference (bitfireAT/davx5#199) 2023-02-01 13:10:35 +01:00
Sunik Kupfer
ca62efa2aa remove DaoTools (for syncing) (bitfireAT/davx5#190)
* add test fixtures

* Use direct DB access instead of DaoTools

* minor changes and kdoc

* minor changes and kdoc

* remove obsolete DaoTools, SyncableDao and IdEntity classes

* KDoc

* use hashmap instead of list and added kdoc

* always load from database

* add a test and make test structur more flexible for future tests

* minor KDoc

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-01-31 13:40:39 +01:00
Ricki Hirner
2661dbba34 Use Worker for account cleanup (bitfireAT/davx5#197)
* [WIP] Use Worker for account cleanup

* Remove AccountsUpdatedListener Hilt module

* Use 15 seconds, provide Hilt module for App creation
2023-01-28 21:09:58 +01:00
Ricki Hirner
954f4dcaa3 Debug info: count jtx Board tasks correctly (bitfireAT/davx5#195)
* Reformat code

* Use sync-adapter URI to count jtx Board entries

* Debug info: restrict entry counts per withSyncAdapter URI and not over WHERE clause
2023-01-28 21:09:49 +01:00
Ricki Hirner
6c8976f6ff Version bump to 4.3-beta.1 2023-01-20 11:12:33 +01:00
Ricki Hirner
29b2b2372f Catch SecurityException of notifications 2023-01-20 11:12:32 +01:00
Ricki Hirner
5156ff1380 Update dependencies 2023-01-20 11:12:32 +01:00
Sunik Kupfer
62617a2889 168 refactor service detection part 2 (bitfireAT/davx5#174)
* [WIP] refactor

* [WIP] refactor

* save, update and delete homesets one by one

* save, update and delete collections one by one

* cleaner code

* prevent jumps in row ids

* [WIP] small changes and kdoc

* remove duplicate code and add kdoc

* improve kdoc

* remove redundancy based on service type in resource finder

* tests setup

* handle cancellation

* add tests

* Don't use IdEntity for service detection anymore

* HomeSetDao: getByUrl requires a service (there may be two accounts with the same homeset URLs, for instance with different credentials)

* Deprecate DaoTools

* Minor changes

* Add TODO

* use self explanatory variables instead of a pair

* update kdoc

* add unfinished tests

* add test for updating a collection

* add test for preserving collection flags

* mark collections as homeless if not rediscovered in its homeset

* proper implementation of update and delete of homeless collections with test

* minor changes and kdoc

* Tests: adapt mock server 404

* KDoc

* get collections by service and url, deprecate getByUrl()

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-01-19 23:32:15 +01:00
Patrick Lang
df2922f873 Added intent-filter for AccountActivity (bitfireAT/davx5#187)
* Added intent-filter for AccountActivity

* removed intent filter for account activity
2023-01-19 23:31:57 +01:00
Flo Edelmann
861bcf30b4 Add dav.mailbox.org to known base URLs (#251)
* Add dav.mailbox.org to known base URLs

* Replace mailbox.org by dav.mailbox.org

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-01-11 23:51:35 +01:00
Arnau Mora
bee7cf20ac 169 webdav handle weak etags correctly (bitfireAT/davx5#185)
* Added `GetETag.NAME` fetching when `PROPFIND`ing

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* `GetETag.eTag` is only taken if tag is not weak

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Using `GetETag` instead of `QuotedStringUtils` and ignoring weak eTags

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Updated `dav4jvm`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Fixed checks

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Use new dav4jvm where weak can't be null

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2023-01-09 17:57:04 +01:00
Sunik Kupfer
5434291c6c fixes data saver warning showing below android 7 (bitfireAT/davx5#180)
post false to live boolean dataSaverEnabled even if below api lvl 24
2023-01-09 17:56:56 +01:00
Bernhard Stockmann
8dc9f2e415 Update README.md 2023-01-04 14:55:11 +01:00
Arnau Mora
53bd4866c3 Added Mastodon link to navigation drawer (bitfireAT/davx5#179)
* Added mastodon icon

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added mastodon link to drawer

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added mastodon link action

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Mastodon icon tint

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added Mastodon icon to main

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
2023-01-01 23:26:54 +01:00
Michael Biebl
1f4398b21e Use consistent margins between different permission switches 2022-12-22 21:25:12 +01:00
Ricki Hirner
12bb09ecb8 Minor CI changes 2022-12-22 21:22:56 +01:00
Ricki Hirner
4c71d21d7b WorkManager address book sync: don't run calendar/tasks sync on address book accounts; vcard4android: set N:LastName for vCard3 groups 2022-12-22 21:22:56 +01:00
Patrick Lang
1f818ee3b4 Added language selection (bitfireAT/davx5#137)
* added language selection

* PermissionsFragment: Hide notification permission switch on Android < 13

* Retrieve locales with a function

* Added locales flavoring

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Moved locales functions to `locales.gradle`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added `locales_config.xml` generation

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Now gets generated automatically

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added `android:localeConfig`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Updated submodules

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Using `logger` instead of `println`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
Co-authored-by: Arnau Mora <arnyminer.z@gmail.com>
2022-12-22 21:22:55 +01:00
Michael Biebl
0521f4bcf0 Trivial indentation fixes (#226) 2022-12-22 21:22:55 +01:00
Ricki Hirner
2730d14cb9 Version bump to 4.3-alpha.1 2022-12-22 21:22:55 +01:00
Sunik Kupfer
cfee0f3461 work manager manual sync (bitfireAT/davx5#109)
* rabase with dev branch

* added test for checking whether manual work-manager sync queues worker

* overwrite getForegroundInfo to show a "sync running" notification, to run expedited work on Android <12

* basic error state with sensible feedback from syncframework

* remove integer state flags and pass SyncResult as string

* Manual sync cancellation

* rabase with dev branch

* Minor changes

- add Jtx Board sync adapter to sync worker
- use new notification ID for sync worker

* status bar reflects sync of SyncWorker and sync framework correctly

* [WIP] custom hilt SyncComponent

* fix autoclose cast not available below api24

* SyncScope implementation using WeakReference

* Remove unnecessary logging call

* AddressBooksSyncAdapter.sync uses SyncWorker instead of ContentResolve to call requestSync

* add some code documentation

* move all utility objects into one package

* Also check SyncWorker state for accounts list sync status bar

* clean up imports

* Remove duplicate copyright notices

* Minor changes

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-12-15 16:04:39 +01:00
Michael Biebl
262592a3d9 Remove duplicate copyright header (#218) 2022-12-11 17:03:27 +01:00
Ricki Hirner
18a3c0e2fd Fetch Translations from Transifex 2022-12-10 13:20:31 +01:00
Ricki Hirner
5374afb7ae Version bump to 4.2.6 2022-12-10 13:17:59 +01:00
Álvaro Brey
9ceee1b6c0 LoginSetupActivity: allow http/https in intent filter (#211)
* LoginSetupActivity: allow http/https in intent filter

Initially introduced in 054f1ece61,
this `intent-filter` breaks login from the Nextcloud app, as we send the server URI as `http`/`https`.

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>

* Update intent-filter for login flow on api lvl 33 (closes bitfireAT/davx5#172)

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2022-12-08 17:48:13 +01:00
Sunik Kupfer
e6ecc7a9b0 Remove forced wifi-only sync when Data Saver is active (bitfireAT/davx5#171)
* Remove forced wifi-only sync when Data Saver is active

* move data saver warning to AppWarningsManager

* Minor changes

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-12-08 11:56:47 +01:00
Sunik Kupfer
1b17124b80 move warning notification logic into a warning class (bitfireAT/davx5#170)
* move warning notification logic into a warning class

* Move Warnings to UI package

* Minor changes

* Move "global sync disabled" warning to AccountListFragment, too

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-12-08 11:52:56 +01:00
Sunik Kupfer
02885947da Refactor DavService to WorkManager (bitfireAT/#164)
* SyncManager: remove retry intent
* refactor DavService to RefreshCollectionsWorker now using WorkManager
* move DavResourceFinder to new service detection package
* Optimize imports

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-12-08 11:52:38 +01:00
Ricki Hirner
3f2fcda6d3 Add Github sponsoring 2022-11-30 10:25:15 +01:00
Sunik Kupfer
ba46c4793a Update F-Droid changelog 2022-11-29 15:13:24 +01:00
Sunik Kupfer
405ef4d0b8 Version bump to 4.2.5 2022-11-29 15:09:24 +01:00
Sunik Kupfer
5e099561c1 Fetch translations from Transifex 2022-11-29 15:07:22 +01:00
Ricki Hirner
dd9ac77705 Add referrer with utm_source to task app installation Intents 2022-11-29 15:04:34 +01:00
Ricki Hirner
369d5e49f1 Update dependencies, version bump to 4.2.5-rc.1 2022-11-24 18:14:26 +01:00
Sunik Kupfer
5222c13a23 Support read-only task lists for Tasks.org (bitfireAT/davx5#160)
* [WIP] save task list access level to android task list for tasks.org to use
* Disrecommend using OpenTasks in selection dialog
* Update string, use matching ical4android

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-11-24 17:33:37 +01:00
Ricki Hirner
b17b38ff50 Update dependencies, including ical4android 2022-11-09 13:16:59 +01:00
Ricki Hirner
3b74ad5257 PermissionsFragment: Hide notification permission switch on Android < 13 2022-11-04 17:53:30 +01:00
Clément GIBAUD
e4a5c47887 Add framagenda to pre-set URLs (#192) 2022-11-04 16:31:24 +01:00
Sunik Kupfer
8915a3c103 android 13 compatibility (bitfireAT/davx5#155)
* check for notify permission before notifying

* [WIP] add notification permission request

* Revert "check for notify permission before notifying"

This reverts commit ed8e046d73163bef5684622bd877f08b9ee781c6.

* add notification permission request

* [WIP] add notifications disabled card in accounts view

* add notifications disabled card in accounts view

* reorder permissions

* use dp instead of mm for image max height in permissions view

* stop using array to bundle notification permissions

* Use new permissions contract for PermissionsFragment

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-11-01 12:51:20 +01:00
Ricki Hirner
43ae3fcd1f Update libraries 2022-10-29 16:42:05 +02:00
Ricki Hirner
796eddb975 Update dav4jvm, Kotlin 2022-10-28 13:34:32 +02:00
Ricki Hirner
57a77bd5f5 Actually update vcard4android (better read-only contacts support) 2022-10-28 13:15:38 +02:00
Sunik Kupfer
d32b21a465 Update F-Droid changelog 2022-10-25 11:22:34 +02:00
Sunik Kupfer
31a61f508d Version bump to 4.2.4 2022-10-25 11:16:53 +02:00
Ricki Hirner
a8e21bed8b Force downloading all contacts after resetting read-only contacts when collection supports Collection Sync 2022-10-21 13:41:22 +02:00
Ricki Hirner
7b70cf45fc Fetch translations from Transifex 2022-10-21 13:41:22 +02:00
Ricki Hirner
66282c10e6 Version bump to 4.2.4-rc.1 2022-10-21 13:41:22 +02:00
Ricki Hirner
a493f0a9a9 Update dependencies 2022-10-21 13:41:22 +02:00
Ricki Hirner
951c29e66a Force sync after resetting read-only contacts (bitfireAT/davx5#154)
* Force sync after resetting read-only contacts

* Rename variable for clarity
2022-10-21 13:41:21 +02:00
Sunik Kupfer
0f124cf5b5 LocalAddressBook: update read-only flag of groups, too (bitfireAT/davx5#153)
* LocalAddressBook: update read-only flag of groups, too

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-10-21 13:41:21 +02:00
Sunik Kupfer
9bd564b2c6 fix crash when accounts vanish (closes bitfireAT/davx5#140) (bitfireAT/davx5#148) 2022-10-21 13:41:21 +02:00
Ricki Hirner
bec9f8bcfb Fetch translations from Transifex (including new translations) 2022-10-14 23:06:56 +02:00
Ricki Hirner
d2614bd87e Create an option to pre-select the read-only setting for address books (bitfireAT/davx5#141)
* [WIP] add managed restriction to force read-only addressbooks
* Honor app-wide read-only address book setting when syncing address books
* reflect status of force read-only address books setting in the GUI

Co-authored-by: Sunik Kupfer <kupfer@bitfire.at>
2022-10-12 17:35:12 +02:00
Ricki Hirner
e14460111e Use Material switches for preferences 2022-10-11 12:48:16 +02:00
Ricki Hirner
31fb7ec190 CalDAV/CardDAV UI: replace checkbox by switch (bitfireAT/davx5#143)
Closes bitfireAT/davx5#142
2022-10-11 10:15:01 +02:00
Ricki Hirner
99710ca94e Update Manual URL 2022-10-08 18:58:31 +02:00
Ricki Hirner
c635dc8aa3 Add pre-set base URLs 2022-10-03 10:51:33 +02:00
Ricki Hirner
ce3f3b7cc4 Update vcard4android (disable LABEL generation); version bump to 4.2.4-beta.1 2022-10-03 10:51:12 +02:00
Ricki Hirner
2b16cd6365 Lint 2022-09-27 13:03:08 +02:00
Ricki Hirner
8a7f51b705 Update dependencies, compile SDK level, build tools, gradle plugin 2022-09-27 13:03:08 +02:00
Ricki Hirner
f5b08b60bf Update ical4j (handle NPE when time zone is known to Android but not to ical4j) 2022-09-27 13:03:06 +02:00
Ricki Hirner
fc819eb001 Update README
- closes #153
- closes #152
2022-09-27 11:33:07 +02:00
Ricki Hirner
fbbf2a6b07 Add calendar and contacts provider to manifest/queries (for increased compatibility with apps like Huawei calendar) 2022-09-23 13:32:22 +02:00
Ricki Hirner
8ff90954cd Don't run AccountsUpdatedListener while account is being renamed
Don't run AccountsUpdatedListener while account is being renamed (closes bitfireAT/davx5#135)
2022-09-22 20:55:08 +02:00
Sandelinos
a7fcc087bc Add monochrome icon (#151) 2022-09-20 20:32:58 +02:00
Ricki Hirner
e2c2e566b9 Bump version to 4.2.3.4 2022-09-20 18:50:24 +02:00
Ricki Hirner
d4eaa0718a Fetch translations from Transifex 2022-09-19 16:36:52 +02:00
Ricki Hirner
e3c44f2e50 Update ical4android (enable relaxed valiation of email addresses); bump version to 4.2.3.4-rc.2 2022-09-19 16:35:02 +02:00
Ricki Hirner
c517c9c3b5 Bump version to 4.2.3.4-rc1 2022-09-19 16:35:01 +02:00
Ricki Hirner
757c01c186 Update dependencies (including ical4android to address Kiev/Kyiv bug)
See https://github.com/bitfireAT/davx5-ose/discussions/133
2022-09-19 16:35:01 +02:00
Ricki Hirner
97eb45ec12 Fetch translations from Transifex 2022-09-08 22:04:35 +02:00
Ricki Hirner
d7102f94bd Version bump to 4.2.3.3 2022-09-08 22:03:43 +02:00
Ricki Hirner
cf76cc16ce Update ical4android 2022-09-08 22:03:43 +02:00
Sunik Kupfer
78e4cbe06d roll back commons-io to 2.8.0 for API levels <26 (closes bitfireAT/davx5#130) 2022-09-07 12:23:12 +02:00
Ricki Hirner
f47adbef4a Wrap GrantPermissionRule into InitCalendarProviderRule
Closes bitfireAT/davx5#127
2022-08-31 17:16:56 +02:00
Sunik Kupfer
94c9f869c9 Generate new filename if invalid for upload (closes bitfireAT/davx5#110)
* use random UUID instead of UID as upload file name if UID would be dangerous

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-08-31 17:16:54 +02:00
Ricki Hirner
ee993a8557 Rename "standard" flavor to "ose" 2022-08-31 17:12:25 +02:00
Ricki Hirner
03deb4191a Bump version to 4.2.3.2 2022-08-29 15:07:51 +02:00
Ricki Hirner
b5c16ddbd4 Fetch translations from Transifex 2022-08-29 15:07:50 +02:00
Ricki Hirner
f054ab43b9 Update ical4android 2022-08-29 15:07:49 +02:00
Ricki Hirner
aeb4541eb9 Version bump to 4.2.3.1 2022-08-24 16:56:19 +02:00
Ricki Hirner
7144b963e4 Version bump to 4.2.3.1-rc1 2022-08-23 22:00:08 +02:00
Ricki Hirner
f38e7b2dab Update hilt and ical4android (ical4android had dependency that caused JSONObject problem in release builds) 2022-08-23 21:51:54 +02:00
Ricki Hirner
62912dc83a Add F-Droid changelog 2022-08-23 12:01:17 +02:00
Ricki Hirner
acc3ca1b4c Version bump to 4.2.3 2022-08-23 11:36:32 +02:00
Ricki Hirner
fec8eae9d0 Update dependencies, including ical4android 2022-08-19 12:39:32 +02:00
Ricki Hirner
885035fadf Update dependencies 2022-08-19 12:39:32 +02:00
Sunik Kupfer
e5604118fd confirmation dialog for removing webdav mount (closes bitfireAT/davx5#47) (#119)
* confirmation dialog for removing webdav mount

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-08-19 12:39:32 +02:00
Ricki Hirner
20e79e49b3 Rename notification for external file logging 2022-08-15 12:41:50 +02:00
Ricki Hirner
b3e0cef0a6 Fix Android10ResolverTest when IPv6 is the primary address type 2022-08-15 12:41:49 +02:00
Michael Biebl
7bb97ffa31 Remove unused colors (#110)
They are a left over from
commit 12802f8697 and the incomplete
removal in commit 054f1ece61
2022-08-10 17:09:35 +02:00
Ricki Hirner
fbb7c6d161 Update dav4jvm, gradle plugin 2022-08-08 20:52:03 +02:00
Ricki Hirner
4f143318da Update dependencies and ical4android 2022-07-28 15:12:34 +02:00
Ricki Hirner
8aefab65f6 Update dependencies 2022-07-28 15:01:23 +02:00
Ricki Hirner
38c40d9466 Add authors 2022-06-21 14:45:14 +02:00
Sunik Kupfer
5daae1b74d Update F-Droid changelog 2022-06-21 12:50:25 +02:00
Sunik Kupfer
8c22b4ed3b Version bump to 4.2.2 2022-06-21 12:35:05 +02:00
Sunik Kupfer
dc94791637 etch translations from Transifex 2022-06-21 12:09:04 +02:00
Sunik Kupfer
d2c0a2bb44 use findActivity() provided by hilt (closes bitfireAT/davx5#106) (#107)
* use FragmentComponentManagers findActivity method provided by hilt (closes bitfireAT/davx5#106)

* WebCalFragment CalendarViewHolder: Pass Fragment instead of FragmentManager so that requireActivity() can be used

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-06-21 12:05:43 +02:00
Patrick Lang
c90e0bf085 Removed update related to from JtxSyncManager (#92)
* Removed update related to from JtxSyncManager

updateRelatedTo is now done implicitely in the sync content provider for every inserted or updated icalobject/relatedto

* Update ical4android

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-06-21 12:04:12 +02:00
Sunik Kupfer
2e6ce88c58 non-flaky tests (#103)
* [WIP] initialization code to make tests non-flaky

* init code as junit rule and remove flaky annotations

* remove exception for flaky tests in Github test workflow

* ensure correct class rule execution order
2022-06-21 12:02:28 +02:00
Patrick Lang
5b0788b2e9 Don't set account visibility if it was already visible (#102) 2022-06-21 11:55:59 +02:00
Ricki Hirner
f813392c72 Version bump to 4.2.1.2-alpha.3 2022-05-30 14:49:47 +02:00
Ricki Hirner
bd7d6c7b4d AccountSettings: handle address book accounts (#101)
* AccountSettings: handle address book accounts (fixes: address books are not synced because of IllegalArgumentException)

* Tests (not finished)
2022-05-30 14:49:47 +02:00
Ricki Hirner
0e93a47d6d Version bump to 4.2.1.2-alpha.2 2022-05-29 15:03:37 +02:00
Ricki Hirner
99325e8930 Sync adapter: improve HttpClient and cancellation (#100)
* Sync adapter: share single HttpClient between all SyncManagers (should fix bitfireAT/davx5#99)
* HttpClient: use producer for CertManager
* Call setAccountVisibility only when necessary
* Fix tests
2022-05-29 15:03:36 +02:00
Ricki Hirner
0a11a0bcfc Don't set up periodic syncs for contacts sync of the address book acc… (#97)
Don't set up periodic syncs for contacts sync of the address book accounts (fixes bitfireAT/davx5#96)
2022-05-29 15:02:17 +02:00
Ricki Hirner
f3c79367ef Version bump to 4.2.1.2-alpha.1 2022-05-29 15:01:57 +02:00
Ricki Hirner
25ea4fc2ca Create SECURITY.md 2022-05-28 20:06:35 +02:00
Ricki Hirner
4dfebe4355 Hilt: use intro OpenSourceFragment 2022-05-28 19:19:43 +02:00
Ricki Hirner
b51d18a659 Use Hilt instead of Koin (#93)
* Use Hilt for DI
* Use Hilt for settings providers
* Use Hilt for login credentials fragment
* Use Hilt instead of ServiceLoader for intro fragments
* Rewrite from Koin to Hilt
* Tests
* Use more Hilt modules for service loading (account drawer, intro fragments)
* Lint
* WebDAV provider: don't listen to changes in onCreate (causes problems with Hilt and tests)
2022-05-28 11:05:35 +02:00
Ricki Hirner
07ba158e66 Fix network security policy with tests 2022-05-27 14:36:25 +02:00
Patrick Lang
338d0d4691 Moved jtx Board in TaskProvider selection to first position (#94) 2022-05-27 14:35:59 +02:00
Bernhard Stockmann
7f58e3e4a8 Update images, add video URL (#49)
* Update images, add video URL

* Video without ab_channel
2022-05-26 12:05:11 +02:00
Michael Biebl
b71452195e Remove duplicate copyright header (#46) 2022-05-23 11:31:47 +02:00
Ricki Hirner
cf3be750d0 Fetch translations from Transifex 2022-05-22 23:50:22 +02:00
Ricki Hirner
212392e78c Version bump to 4.2.1.1 2022-05-22 23:49:45 +02:00
Sunik Kupfer
ddae93c225 Add mocck dependency for tests 2022-05-22 23:48:00 +02:00
Ricki Hirner
1f4a25e4a4 Use Koin for all tests; CI: run tests for all flavors (#91)
Closes bitfireAT/davx5#89
2022-05-22 18:56:47 +02:00
Ricki Hirner
84f55bc39a Fix Android10Resolver test (requires Android 10 instead of 8) 2022-05-12 10:46:49 +02:00
Ricki Hirner
8384730aff Refactor some models to use proper constructors instead of custom initialize() methods 2022-05-12 10:46:49 +02:00
Ricki Hirner
8ca3d857d3 Use Koin Dependency Injection for Singletons (#84) 2022-05-12 10:46:48 +02:00
Ricki Hirner
12cc2cfcde Update submodules 2022-05-12 10:39:28 +02:00
Ricki Hirner
7d424183a8 Update gradle plugin, Kotlin, AndroidX Material 2022-05-12 10:39:26 +02:00
Ricki Hirner
d3c10c36b0 CI: create Github release for new tags 2022-05-05 15:21:57 +02:00
Ricki Hirner
f37f3e5104 Bump version to 4.2.1 2022-05-05 14:25:38 +02:00
Ricki Hirner
541f51f77e Version bump to 4.2.1-rc.2 2022-04-29 16:03:59 +02:00
Ricki Hirner
9bc4b0a837 Fetch translations from Transifex 2022-04-25 14:52:53 +02:00
Ricki Hirner
f31c3cab3d Move EarnBadgesActivity strings to gplay 2022-04-25 14:50:35 +02:00
Ricki Hirner
776adedfbf Version bump to 4.2.1-rc.1 2022-04-25 14:25:36 +02:00
Ricki Hirner
054f1ece61 Setup intent filter (#80)
* Add intent filter for caldav(s)://, carddav(s):// and davx5:// schemes
* Define intent-filter only for standard and gplay flavors
* Merge manifest; remove Espresso tests (further tests should be added)
* Lint

Closes bitfireAT/davx5#77
2022-04-25 14:25:02 +02:00
Ricki Hirner
eaea4936d7 ical4android: don't crash on RDATEs with PERIOD 2022-04-25 14:20:51 +02:00
Ricki Hirner
6b98088922 Update dav4jvm 2022-04-24 10:38:17 +02:00
Ricki Hirner
5d61487a85 Rename at.bitfire.davdroid.model to at.bitfire.davdroid.db to make clear it contains database models (and not view models) 2022-04-24 10:38:17 +02:00
Ricki Hirner
d4b82d37be Version bump to 4.2.1-beta.2 2022-04-21 21:18:32 +02:00
Ricki Hirner
ffea72bc7c Update dav4jvm for improved service compatibility 2022-04-21 21:18:08 +02:00
Ricki Hirner
f4b157a101 Server handles only tasks not jouranls (#79)
* Service detection: update properties of already existing collections which are not in homesets
* Trigger service detection after creating a collection because the server may change properties
* jtx sync: query tasks/journals only when supported

Should fix bitfireAT/davx5#78
2022-04-20 17:57:11 +02:00
Ricki Hirner
c283691399 jtx Board sync: show whether VTODO and/or VJOURNAL components are supported by a collection; update ical4android 2022-04-20 17:57:09 +02:00
Ricki Hirner
e2c48df68f Version bump to 4.2.1-beta.1 2022-04-15 12:22:42 +02:00
Ricki Hirner
40941f9bce Move file, update gradle and plugin 2022-04-15 12:12:17 +02:00
Sunik Kupfer
12802f8697 Mirror changes in AccountsDrawerHandler
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-04-15 12:01:09 +02:00
Michael Biebl
3c0a26bb0c Preferences (#25)
* Set InputType for proxy_host and proxy_port programmatically

Since AndroidX it is no longer sufficient to simply specify
android:inputType in settings_app.xml.

* Drop custom IntEditTextPreference.kt

Doesn't appear to be necessary anymore.
2022-04-11 11:24:24 +02:00
Ricki Hirner
08dfa3f048 Fetch translations from Transifex 2022-03-23 18:09:47 +01:00
Ricki Hirner
cd4aa0891b ical4android fix; raise SDK level to 32; bump version to 4.2.0.3 2022-03-23 18:09:02 +01:00
Patrick Lang
c8723d0f64 propagate privWriteContent as read only collection (#71) 2022-03-23 18:08:34 +01:00
Ricki Hirner
5ee6e57b03 Fetch translations from Transifex 2022-03-22 13:10:56 +01:00
Ricki Hirner
48bd99e0b6 Version bump to 4.2.0.2 2022-03-22 13:05:23 +01:00
Ricki Hirner
ab806e458a vcard4android: contact photo improvements 2022-03-22 12:55:42 +01:00
Ricki Hirner
2c785684a2 Navigation drawer: update Community 2022-03-20 20:26:45 +01:00
Ricki Hirner
522fc8763f Fetch translations from Transifex 2022-03-20 20:26:17 +01:00
Ricki Hirner
df08cf6074 Version bump to 4.2.0.1 2022-03-20 20:25:32 +01:00
Ricki Hirner
763da2221c Contact photos: always output JPEG (vcard4android); sync: query max-resource-size (at the moment only for debugging) 2022-03-20 20:25:14 +01:00
Ricki Hirner
850b1f54a8 vcard4android: better handling of (invalid) photos; reduce RAM usage by reducing sync threads a bit 2022-03-19 18:44:53 +01:00
Ricki Hirner
f24c591283 F-Droid changelogs 2022-03-18 11:05:04 +01:00
Ricki Hirner
b8d83cf46f Fetch translations from Transifex 2022-03-18 10:36:21 +01:00
Ricki Hirner
ec7d59a46b Version bump to 4.2 2022-03-18 10:36:05 +01:00
Ricki Hirner
66ebe4f224 Update dav4jvm (fixes bitfireAT/davx5#69) 2022-03-18 10:35:44 +01:00
Ricki Hirner
abb60a4331 Fetch translations from Transifex 2022-03-16 14:43:44 +01:00
Ricki Hirner
4950a2390d Version bump to 4.2-rc.1 2022-03-16 14:43:43 +01:00
Ricki Hirner
49ba32e64f Update dependencies 2022-03-16 14:43:43 +01:00
Ricki Hirner
da5ff1ea39 Move OnAccountsUpdateListener to App (may fix bitfireAT/davx5#45) 2022-03-16 14:43:43 +01:00
Ricki Hirner
da4b5cf143 DebugInfo: provide IntentBuilder that truncates large logs/local resource dumps (should fix bitfireAT/davx5#69) 2022-03-16 14:43:42 +01:00
Ricki Hirner
248dd6fdb3 Update dependencies (including vcard4android with new photo handling) 2022-03-16 14:43:42 +01:00
Ricki Hirner
979123b9ef Add option to select detected collections for synchronization automatically 2022-03-16 14:43:37 +01:00
Sunik Kupfer
7367477ebe Add option to use socks proxy for orbot (closes bitfireAT/davx5#28) (#64)
* Add option to use SOCKS proxy for Orbot
* Fix tests, add constants
* Add option for "no proxy", add migration
* Improve UI

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-03-09 12:51:31 +01:00
Ricki Hirner
8fb9b3a86e Update dependencies; bump version to 4.2.0-beta.1 2022-03-06 11:47:39 +01:00
Sunik Kupfer
a7779ca6c3 billing and play dependencies (bitfireAT/davx5#62) (#63)
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-03-06 11:47:29 +01:00
Ricki Hirner
73a7b718e2 Update dependencies; version bump to 4.2.0-alpha.3 2022-03-06 11:44:12 +01:00
Patrick Lang
4084dc36a2 Updated queryCapabilities to use the same code as in TasksSyncManager, added try-catch block for sync-method (#60)
* Updated queryCapabilities to use the same code as in TasksSyncManager
Might solve #59

* Added try-catch block for sync-method
updated min version for JtxBoard in TaskProvider.kt
Should solve #59

* removed syncResult.databaseError = true

* Changed wrong file before

* Created TasksSyncUtils to put notifyProviderTooOld method

* Cleaned up imports

* Added Version check in JtxSyncAdapterService.kt, required TaskProvider.checkVersion to be public

* Minor changes

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-03-06 11:41:33 +01:00
Ricki Hirner
7534fe689b Update ical4android 2022-03-06 11:41:33 +01:00
Ricki Hirner
955c09d518 Update gradle plugin and build tools 2022-03-06 11:41:28 +01:00
Ricki Hirner
7493d1eda8 Version bump to 4.2.0-alpha.2 2022-02-24 11:15:42 +01:00
Ricki Hirner
ff3b5f50af Update ical4android to fix problem with Jtx Board relations 2022-02-24 11:15:26 +01:00
Ricki Hirner
1554c6ae1c Version bump to 4.2.0-alpha.1; update dependencies 2022-02-09 13:43:57 +01:00
Patrick Lang
16311708f8 Jtx Board synchronization (#56)
Support tasks and notes synchronization with Jtx Board

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-02-09 13:43:17 +01:00
Ricki Hirner
6ab291bdb2 Update gradle plugin 2022-02-09 13:42:37 +01:00
Ricki Hirner
19be3c34bd Version bump to 4.1.1; add F-Droid changelog 2022-02-01 11:00:55 +01:00
Ricki Hirner
64bd373611 Fetch translations from Transifex 2022-02-01 10:46:14 +01:00
Ricki Hirner
b2a5a1b1f3 Version bump to 4.1.1-rc.1 2022-01-28 17:06:26 +01:00
Ricki Hirner
6af033a351 Avoid UninitializedPropertyAccessException when accessing collectionURL for logging the sync 2022-01-27 14:11:20 +01:00
Ricki Hirner
1532fab49e Use asSyncAdapter() for calendar provider URIs 2022-01-27 12:18:09 +01:00
Ricki Hirner
62af6f45ab Update gradle plugin 2022-01-27 12:18:09 +01:00
Ricki Hirner
2ec33002c8 Version bump to 4.1.1-beta1 2022-01-24 11:23:51 +01:00
Ricki Hirner
81aa95223f Fetch translations from Transifex 2022-01-24 11:14:50 +01:00
Ricki Hirner
c1125a5bd0 Update dependencies 2022-01-24 11:14:22 +01:00
Sunik Kupfer
7593229623 Delete dirty events without instances before syncing
Delete dirty events without instances before syncing (resolves bitfireAT/davx5#34)

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-01-22 10:55:32 +01:00
Ricki Hirner
87725a3b5d Show a warning when disk space is low, which will stop synchronization (resolves bitfireAT/davx5#44) 2022-01-17 18:22:45 +01:00
Ricki Hirner
708c80fa0a Update cert4android and vcard4android (only copyright) 2022-01-13 14:18:23 +01:00
Ricki Hirner
6c34737cbc Fix that foreground service notification was always shown (fix for bitfireAT/davx5#32) 2022-01-13 14:18:23 +01:00
Sunik Kupfer
989cb1fd99 Fix foreground service start not allowed exception (resolves bitfireAT/davx5#32)
* Make "Battery optimization whitelisting" a global DAVx5 setting
* Make "Keep in foreground" setting dependent of "Battery optimization whitelisting" setting
* if foreground service is enabled, remind user to enable battery optimization whitelisting too, before starting the foreground service

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
2022-01-13 14:18:23 +01:00
Sunik Kupfer
c9b88b0c73 Update ical4j (Closes bitfireAT/davx5#37) (#43) 2022-01-13 14:18:23 +01:00
Ricki Hirner
161157a37c Fetch translations from Transifex 2022-01-05 16:20:12 +01:00
sunkup
9ac05f4f51 Add warning to avoid apostrophes in account names
Add warning to avoid apostrophes in account names. Resolves bitfireAT/davx5#5
2022-01-05 16:18:45 +01:00
sunkup
ed08cc8d15 update dependencies (#33) 2022-01-04 12:38:01 +01:00
Ricki Hirner
197432a245 README: Add Twitter and Website 2021-12-30 19:22:59 +01:00
Ricki Hirner
32d215b3a4 Add F-Droid changelog 2021-12-26 13:41:42 +01:00
Ricki Hirner
d08a1ec737 Version bump to 4.1; update vcard4android 2021-12-20 17:48:13 +01:00
Ricki Hirner
ccd738b4ff Update vcard4android: always add FN (also for vCard4) 2021-12-20 17:47:57 +01:00
Ricki Hirner
985dcf09d3 Fetch translations from Transifex 2021-12-16 13:37:18 +01:00
Ricki Hirner
2b940bfcd7 Version bump to 4.1-rc.1 2021-12-16 13:36:55 +01:00
Ricki Hirner
22e4c75064 WebDAV mounts: add empty view manual link (resolves bitfireAT/davx5#29); update dependencies 2021-12-16 13:36:38 +01:00
Ricki Hirner
c7fd893b31 Update copyright (resolves bitfireAT/davx5#26) 2021-12-15 17:55:00 +01:00
Ricki Hirner
ac7e717cde WebDAV mounts activity: add Help icon 2021-12-15 17:48:30 +01:00
Ricki Hirner
0009429b68 Update dependencies 2021-12-12 16:50:53 +01:00
Ricki Hirner
4b49df77ed Update room, version bump to 4.1-alpha.3 2021-12-03 16:57:09 +01:00
Ricki Hirner
3c6d1172cf Optimize singletons and companion objects (resolves bitfireAT/davx5#25) 2021-12-03 16:57:09 +01:00
Ricki Hirner
6960a48021 Change static singletons to cached singletons with weak references to save memory when the objects are not needed anymore 2021-12-03 15:46:29 +01:00
Ricki Hirner
54d1b5ec2e Refactor WebDAV cache 2021-12-03 15:46:29 +01:00
Ricki Hirner
99ecfb1ca3 WebDAV mounts: add empty view; add navigation drawer entry for Managed DAVx5 (closes bitfireAT/davx5#23) 2021-12-03 15:46:09 +01:00
Ricki Hirner
f1a441c27d WebDAV: accept compliance class 2 or 3 (closes bitfireAT/davx5#21); fix division by zero in progress
- WebDAV service detection: accept compliance class 2 or 3, because they both imply class 1 (there are servers which don't show class 1 although they MUST)
- WebDAV random access: fix division by zero when file size reported by HEAD is zero
2021-12-03 15:45:31 +01:00
Ricki Hirner
56c7d2d73f Remove dokka because the generated documentation isn't used by anybody; fix dav4jvm reference 2021-11-27 18:07:12 +01:00
Ricki Hirner
be98bef4ab Sync adapter: add tests, move check of simultaneous running syncs to new ConcurrentUtils 2021-11-23 14:34:25 +01:00
Ricki Hirner
1747cb2896 Update dependencies 2021-11-23 14:31:34 +01:00
Ricki Hirner
c82d5cb1b4 WebcalModel: get rid of extra WorkerThread + Handler (were not stopped when fragment was closed) 2021-11-22 19:48:28 +01:00
Ricki Hirner
f1de88a0f1 Android10Resolver: add test 2021-11-19 22:45:52 +01:00
Ricki Hirner
a280de73a3 Update cert4android and vcard4android (only README changes); Android10Resolver: use empty JVM object instead of UUID as handle 2021-11-19 14:41:32 +01:00
Michael Biebl
489e09ea08 Drop AboutInfo override for dav4jvm
With the recent move to GitHub, the AboutInfo override for dav4jvm was
broken as it still used com_gitlab_bitfireAT__dav4jvm whereas it is now:
```
$ ./gradlew findLibraries | grep dav4jvm
bitfireAT/dav4jvm (f1d9354a41) -> com_github_bitfireAT__dav4jvm
Manually requested license: mpl_2_0
```

Since the autogenerated dav4jvm AboutInfo looks fine (see attached
screenshot), remove the override.
2021-11-19 11:58:08 +01:00
Michael Biebl
3403992c8b Fix cert4android project URL
Follow-up for commit 331c102992
2021-11-19 11:52:40 +01:00
Ricki Hirner
9418b4f2a5 Temporarily disable jCard because of nextcloud/server#29693, version bump to 4.1-alpha.3 2021-11-13 13:56:42 +01:00
Ricki Hirner
26b4824124 Small Android10Resolver improvements 2021-11-13 13:56:19 +01:00
Ricki Hirner
2eeb1be833 DiskCacheTest: fixes another possible reason why tests may fail?
(trim deletes the files in modification time order, so if two files have the same
modification time, the order is not well-defined)
2021-11-13 11:09:50 +01:00
Ricki Hirner
db77f43931 Bump version to 4.1-alpha.2 2021-11-12 22:23:36 +01:00
Ricki Hirner
d96e311e8b Improve SRV record resolving (resolves bitfireAT/davx5#18)
- Android 8 to 10: use all DNS servers; prioritize resolvers of active connections
  (before: use only DNS servers of "active" connection, which may be a VPN connection without servers)
- Android 10+: use new DnsResolver API so that we don't need to know DNS servers anymore (also supports DoT etc.)
2021-11-12 22:23:09 +01:00
Ricki Hirner
952a10fb3d WebDAV cache: fix problem that sometimes the tests fail 2021-11-12 01:13:03 +01:00
Ricki Hirner
09384948b5 Github issues: add link to forum; add sponsorships 2021-11-10 13:02:48 +01:00
Ricki Hirner
0a17169a8b Update gradle and dependencies 2021-11-08 09:56:14 +01:00
Ricki Hirner
331c102992 Move to Github 2021-11-07 21:18:08 +01:00
Ricki Hirner
5de0718210 Update dependencies (including dav4jvm on Github instead of Gitlab) 2021-11-06 10:17:55 +01:00
Ricki Hirner
2504a81024 jCard: check MIME type from server; use MediaType for MIME types when possible 2021-11-05 01:38:59 +01:00
Ricki Hirner
ede9e1cf55 CI: use container from ghcr.io 2021-11-04 10:30:35 +01:00
Michael Biebl
cd1b14889b Properly cancel NOTIFY_REFRESH_COLLECTIONS error notifications
When displaying the notification, we use serviceId as tag, so use
serviceId as well when cancelling it.
2021-10-22 12:56:36 +00:00
Ricki Hirner
d8fa5d4979 Version bump to 4.1-alpha.1 2021-10-22 14:52:05 +02:00
Ricki Hirner
563b131211 Support jCard (closes #11) 2021-10-22 14:51:41 +02:00
Ricki Hirner
201719af1a Debug info: show available/total storage memory (closes #10) 2021-10-21 07:10:47 +02:00
Ricki Hirner
290e73229b Update dependencies 2021-10-17 16:06:15 +02:00
Ricki Hirner
265d899d2c WebDAV: always return MIME type; change cached file size/modification time after successful upload 2021-10-15 12:38:18 +02:00
Ricki Hirner
7d4a5863b3 Update gradle plugin 2021-10-14 18:16:06 +02:00
Ricki Hirner
265d75b290 Add F-Droid release notes 2021-10-11 16:57:50 +02:00
Ricki Hirner
918c2647da Version bump to 4.0 2021-10-10 13:53:45 +02:00
Ricki Hirner
1bf66770ef Version bump to 4.0-beta3 2021-10-07 21:50:19 +02:00
Ricki Hirner
75bd04e882 CardDAV KIND/MEMBER: always download group vCards after uploading them to keep groups in sync with server 2021-10-07 21:50:04 +02:00
Ricki Hirner
566d10ef8d Add time zone to debug info 2021-10-06 13:57:22 +02:00
Ricki Hirner
a10b1153ea Improve group assignment 2021-10-05 12:29:10 +02:00
Ricki Hirner
5b6a0a8114 Comments, version bump to 4.0-beta3 2021-10-05 10:19:39 +02:00
Ricki Hirner
e8fb6becf9 Fix Room crash and group processing performance problem 2021-10-05 10:08:14 +02:00
Ricki Hirner
f7eea633fb WebDAV: fix random access without DocumentState; add tests 2021-10-04 19:09:47 +02:00
Ricki Hirner
e22928eeea Fetch translations from Transifex 2021-10-04 17:42:43 +02:00
Ricki Hirner
be63957507 Version bump to 4.0-beta2 2021-10-04 17:42:43 +02:00
Ricki Hirner
764102ae6e vCard4-groups: apply pending memberships after a contact has been downloaded again 2021-10-04 14:35:31 +02:00
Ricki Hirner
459cb538d7 GroupMembershipHandler: ignore memberships in groups that don't exist (anymore) 2021-10-04 11:20:19 +02:00
Ricki Hirner
9ee2930312 WebDAV: Add permanent notification to hopefully avoid being killed because of "excessive cpu usage" 2021-10-04 11:08:50 +02:00
Ricki Hirner
5161fad251 WebDAV: optimize cache 2021-10-03 12:51:33 +02:00
Ricki Hirner
a42517e1ed Improve WebDAV UI 2021-10-02 18:54:43 +02:00
Ricki Hirner
231e54db4f Lint 2021-09-29 13:40:36 +02:00
Ricki Hirner
6e502ae8dc Squashed commit of the following:
commit 56f5f5a6cbad595534148a160678972734ca6cf2
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Wed Sep 29 10:59:23 2021 +0200

    Version bump to 4.0-beta1

commit 2a4d7501d9d6cd572269de21e82c204c5a2a57e8
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 21:45:49 2021 +0200

    Fix UiUtils.launchUri once again

commit 293accaf8f26842d198e9d5c4fc9029cb6ac71cc
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 13:47:06 2021 +0200

    Adjust gradle daemon heap size

commit 7763a51c9bbc7baee36ef71648889f7fb4fe965d
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Sep 28 12:21:37 2021 +0200

    Query and show quota

commit 95f4dbfd8ffadd930aab9fc36dc3865bfee54fb7
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Mon Sep 27 22:16:48 2021 +0200

    Persist cookies over WebDAV sessions

commit 17c9327eb3ef3d9384b28b70aa9637718d34e8a2
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Sun Sep 26 19:54:57 2021 +0200

    Add WebDAV caches

    * add random access page cache
    * add thumbnail cache
    * add HEAD response cache

commit 0e5237f8b178ab66099999f72e657c59c9302d71
Author: Ricki Hirner <hirner@bitfire.at>
Date:   Tue Aug 31 13:25:36 2021 +0200

    Add WebDAV Access over Storage Access Framework
2021-09-29 11:04:44 +02:00
Ricki Hirner
da7d1e1ec6 UiUtils: fix wrong "Please install a browser" message 2021-09-29 11:04:00 +02:00
Ricki Hirner
1a13fbbff4 ical4android: fix attendee problem; version bump to 3.4.1-beta1 2021-09-11 15:02:03 +02:00
Ricki Hirner
425a88f6f7 Fetch translations from Transifex 2021-09-09 19:31:34 +02:00
Ricki Hirner
3afc7c8dbc Version bump to 3.4.0.1 2021-09-09 19:31:17 +02:00
Ricki Hirner
a6854f2461 Merge branch 'dev-3.x-ose' of gitlab.com:bitfireAT/davx5-ose into dev-3.x-ose 2021-09-09 16:35:19 +02:00
Ricki Hirner
fc574f5853 lint 2021-09-09 16:34:52 +02:00
rhi
12a85cbf60 Merge branch 'dev-3.x-ose' into 'dev-3.x-ose'
add app icon to Fastlane

See merge request bitfireAT/davx5-ose!62
2021-09-08 07:56:14 +00:00
Ricki Hirner
e0e69d3ffd Collection list: migrate to Paging3 2021-09-07 23:04:28 +02:00
Hans-Christoph Steiner
17430ab9d1 add app icon to Fastlane 2021-09-07 17:25:07 +02:00
Ricki Hirner
3e77b3ab1b SyncManager syncRemote: use main dispatcher for outer coroutine 2021-09-01 18:03:02 +02:00
Ricki Hirner
700ced1701 Simplify checking of duplicated sync calls (don't use WeakReference but locks instead) 2021-09-01 18:03:02 +02:00
Ricki Hirner
d8a70ca30c ProGuard rules: XmlPullParser 2021-08-31 15:08:17 +02:00
Ricki Hirner
57f11f84c9 Update Gitlab CI 2021-08-31 14:24:03 +02:00
Ricki Hirner
660daf6573 Fetch translations from Transifex 2021-08-31 13:47:54 +02:00
Ricki Hirner
ad657dfad7 Version bump to 3.4 2021-08-31 13:47:19 +02:00
Ricki Hirner
bc8d9b5b8c Update Gitlab CI 2021-08-26 18:22:20 +02:00
Ricki Hirner
925bc6a7b0 Update dav4jvm; group strategy: passing LocalAddressBook instead of ContactSyncManager is enough 2021-08-26 18:22:20 +02:00
Ricki Hirner
514dfdae80 Location permission for SSID restriction: make clear that "precise" location is required in Android 12+ 2021-08-26 12:47:20 +02:00
Ricki Hirner
12a08b4a0b Update dependencies 2021-08-25 17:54:05 +02:00
Ricki Hirner
52c1a69ac6 Version bump to 3.4-beta3 2021-08-23 12:02:14 +02:00
Ricki Hirner
87fdb06fc9 Version bump to 3.4-beta2 2021-08-23 11:08:17 +02:00
Ricki Hirner
450368bd0e Use ThreadPoolExecutor with dynamic number of threads as global sync work dispatcher 2021-08-23 11:08:01 +02:00
Ricki Hirner
bf18d02715 Raise SDK level to 31 (Android 12); explicitly make components with intent-filters (not) exported 2021-08-23 11:08:01 +02:00
Ricki Hirner
43086f3cb9 Fetch translations from Transifex 2021-08-22 20:45:42 +02:00
Ricki Hirner
8973054e85 Version bump to 3.4-beta1 2021-08-22 20:44:48 +02:00
Ricki Hirner
8069657238 Fix tests 2021-08-22 18:50:36 +02:00
Ricki Hirner
9ecb02a059 Update SyncManager test 2021-08-22 15:12:33 +02:00
Ricki Hirner
5307333fcf CardDAV: add tests for new data handlers/builders (also relevant for contact groups) 2021-08-22 14:40:39 +02:00
Ricki Hirner
2700ac51da Refactor group handling 2021-08-22 14:40:39 +02:00
Ricki Hirner
7356a4152f Manage worker threads in Sync*Service, not in SyncManager 2021-08-22 14:40:39 +02:00
Ricki Hirner
1c356c1e17 Use strategies to handle contact groups (decouple from ContactsSyncManager) 2021-08-22 14:40:39 +02:00
Ricki Hirner
5feb9d658f Fetch translations from Transifex 2021-07-31 11:33:37 +02:00
Ricki Hirner
1259a8ae0d Version bump to 3.3.13-beta1 2021-07-31 11:33:01 +02:00
Ricki Hirner
6dd6833446 Better support for vCard contact dates and other properties (great rewrite of vcard4android) 2021-07-31 11:32:39 +02:00
Ricki Hirner
bf1d3ef624 Remove onClick from layout XMLs 2021-07-29 13:08:50 +02:00
Ricki Hirner
9d77b54d89 Update gradle 2021-07-29 11:16:46 +02:00
Ricki Hirner
e580dceb65 AboutActivity: update copyright string to mention contributors 2021-07-26 12:51:28 +02:00
Ricki Hirner
6a9f31d3f0 Use new Flexbox repo 2021-07-25 15:52:54 +02:00
Ricki Hirner
12c1d99a2e Update dependencies, including ical4android 2021-07-25 11:41:01 +02:00
Ricki Hirner
d8ab4ffddd Debug info: attach logcat by default; ical4android: don't crash on too large strings when dumping objects 2021-07-22 21:24:31 +02:00
Ricki Hirner
265d086301 Use ./fetch-translations instead of "tx pull" again (also fetches translators) 2021-07-22 16:02:07 +02:00
Ricki Hirner
3a1844d5b5 Transifex: use "tx pull" instead of scripts/fetch-translations.sh from now on 2021-07-20 13:19:49 +02:00
Ricki Hirner
189d7a31d8 Version bump to 3.3.12 2021-07-18 11:35:04 +02:00
Ricki Hirner
5e49d9c191 Version bump to 3.3.12-rc1 2021-07-16 01:51:55 +02:00
Ricki Hirner
f7a176513a Improve AutoComplete fields, especially in AppCompatDelegate-set dark mode 2021-07-16 01:50:37 +02:00
Ricki Hirner
0da5a967b9 Fetch translations from Transifex 2021-07-14 16:32:01 +02:00
Ricki Hirner
b39321d033 Update cert4android translations 2021-07-14 16:28:44 +02:00
Ricki Hirner
a84b8a5a65 Version bump to 3.3.12 2021-07-14 16:28:43 +02:00
Ricki Hirner
dbd0dd0a9b Lint cleanup 2021-07-12 13:40:13 +02:00
Ricki Hirner
267e73723c Version bump to 3.3.12-beta3 2021-07-12 12:49:16 +02:00
Ricki Hirner
42be478ab6 Update test dependencies 2021-07-12 12:42:56 +02:00
Ricki Hirner
ed76789417 AccountsActivity: use model for data/view separation 2021-07-12 12:36:08 +02:00
Ricki Hirner
ee48c41259 Fetch translations from Transifex 2021-07-08 14:38:49 +02:00
Ricki Hirner
137a916b20 Version bump to 3.3.12-beta2 2021-07-08 14:31:48 +02:00
Ricki Hirner
a7abb34fbe Update dependencies 2021-07-08 14:18:55 +02:00
Ricki Hirner
47d317bce9 Create address book/calendar: set error on TextInputLayout instead of TextInputEditText, optimize layout 2021-07-08 11:16:58 +02:00
Ricki Hirner
b918b49ec2 Provide auto-completion for some common base URLs 2021-07-06 20:51:22 +02:00
Ricki Hirner
235b9b1ac6 Change remaining Spinners to AutoCompleteTextInput fields; update dependencies 2021-07-06 20:51:22 +02:00
Ricki Hirner
663957b7dc Rewrite HomeSetAdapter so that it uses a Material-style text field instead of an old-style Spinner that doesn't work with dark mode anymore 2021-07-02 00:32:15 +02:00
Ricki Hirner
23235991d2 Log last sync time for each collection 2021-07-02 00:32:15 +02:00
Ricki Hirner
b94935db50 SyncManager: remove unused code 2021-06-29 19:30:32 +02:00
Ricki Hirner
6cd2af292d Fetch translations from Transifex 2021-06-29 19:11:37 +02:00
Ricki Hirner
51ee39c4f0 Version bump to 3.3.12-beta1 2021-06-29 19:11:37 +02:00
Ricki Hirner
ed722d574e SyncManager: don't create worker threads when not necessary; shut Executor down after using it 2021-06-29 19:11:37 +02:00
Ricki Hirner
2d388eb3ff Debug info activity: create separate card for archive, provide UI feedback for archive generation 2021-06-25 18:08:47 +02:00
Ricki Hirner
7b4ccb3fd4 Set UI theme directly in App.onCreate() and not in an asynchronous thread to avoid rare occurrences of IllegalStateException 2021-06-25 15:38:58 +02:00
Ricki Hirner
d67f57dd13 Update dependencies 2021-06-25 15:28:45 +02:00
Ricki Hirner
36a8ccf740 Debug info .zip: change MIME type from application/zip to */* 2021-06-21 11:56:02 +02:00
Ricki Hirner
616833c9dc Remove deprecated kotlin-android-extensions in favor of view bindings 2021-06-06 20:46:57 +02:00
Ricki Hirner
520f96f017 Update dependencies 2021-06-06 20:41:52 +02:00
Ricki Hirner
0b4fdaffe2 Debug info: use max compression for .zip 2021-06-06 12:12:37 +02:00
Ricki Hirner
0da6b5b032 Changelog for F-Droid 2021-05-26 12:34:37 +02:00
Ricki Hirner
ad4caecf7d Version bump to 3.3.11 2021-05-26 12:13:39 +02:00
Ricki Hirner
d87789690b Catch DB errors while renaming account 2021-05-23 14:32:03 +02:00
Ricki Hirner
5f5da4681e Fetch translations from Transifex 2021-05-23 14:04:49 +02:00
Ricki Hirner
b87c881f12 Version bump to 3.3.11-rc1 2021-05-23 14:03:42 +02:00
Ricki Hirner
bd45588865 Debug info: show app standby bucket; update AndroidX dependencies; minor night mode updates 2021-05-23 14:03:19 +02:00
Ricki Hirner
be99cb0a18 Debug info: show app standby bucket; update AndroidX dependencies 2021-05-23 14:03:00 +02:00
Ricki Hirner
3545cf30cf Remove comments which are not understood by Transifex 2021-05-11 15:04:29 +02:00
Ricki Hirner
6178f39639 Minor amendments for dark theme 2021-05-08 21:31:43 +02:00
Ricki Hirner
c8eae2c90e Version bump to 3.3.11-beta2 2021-05-07 23:06:06 +02:00
Ricki Hirner
78fb37516f Update ical4j 2021-05-07 23:05:36 +02:00
Ricki Hirner
f74c613b53 Dark theme, animation for WelcomeFragment 2021-05-07 22:54:42 +02:00
Ricki Hirner
9ce481b37a Don't use jCenter anymore 2021-05-04 21:02:19 +02:00
Ricki Hirner
d6dc42eb91 Update gradle plugin and version 2021-05-04 21:02:19 +02:00
Ricki Hirner
20034a8f32 Fix Espresso tests 2021-05-04 21:02:19 +02:00
Ricki Hirner
8b43c6f280 Merge branch 'dev-3.x_EspressoExperimental-ose' into 'dev-3.x-ose'
Dev 3.x espresso experimental ose

See merge request bitfireAT/davx5-ose!59
2021-05-04 17:20:44 +00:00
Patrick Lang
a5c51bc05f Dev 3.x espresso experimental ose 2021-05-04 17:20:44 +00:00
Ricki Hirner
6f398701cc Update dependencies 2021-05-04 15:56:10 +02:00
Ricki Hirner
fc1b5d6a33 Merge branch 'dev-3.x_RTLSupportEnabled-ose' into dev-3.x-ose 2021-05-04 12:02:01 +02:00
Ricki Hirner
b44ed202a3 ical4android: assume 1 day/1 hour for Android events without dtEnd and duration (should not occur, but occurs) 2021-04-21 15:14:34 +02:00
Ricki Hirner
90b61d541e Add Vietnamese translations (thanks!); fetch translations from Transifex 2021-04-19 18:54:22 +02:00
Ricki Hirner
42832f22c9 Version bump to 3.3.11-beta1 2021-04-19 18:52:52 +02:00
Ricki Hirner
dffe5d176b Android 11: fix problems when viewing URLs in some default browsers; vcard4android: don't convert TYPEs into explicit custom labels 2021-04-19 18:52:20 +02:00
Ricki Hirner
7122e344b7 F-Droid changelog for 3.3.10 2021-04-15 17:43:53 +02:00
Ricki Hirner
26900c5d4f Version bump to 3.3.10 2021-04-15 17:37:08 +02:00
Patrick Lang
f7c0ef231b Update .gitlab-ci.yml file 2021-04-13 18:06:48 +00:00
Patrick Lang
5c430664a9 Update .gitlab-ci.yml file, added CURL: - curl -XPOST -d 'email=gitlab%40bitfire.at&password=$DAVTEST_TOKEN&action=Request+access' 'https://davtest.dev001.net/access/' 2021-04-13 17:43:05 +00:00
Ricki Hirner
d9d593a4d6 Fetch translations from Transifex 2021-04-12 23:03:04 +02:00
Ricki Hirner
6790d95b21 Update build tools, ical4android, cert4android 2021-04-12 22:59:55 +02:00
Ricki Hirner
db2c207669 Version bump to 3.3.10-rc1 2021-04-09 20:54:26 +02:00
Ricki Hirner
71d6402ff2 Update dependencies (including ical4j to 3.0.24) 2021-04-09 20:54:06 +02:00
Ricki Hirner
e19ae992ea Client certificates: fix occasional HTTP 400 errors
* don't catch and ignore exceptions when adding client certificates for authentication
* remove useless KeyStore usage
2021-04-06 13:37:42 +02:00
Ricki Hirner
ef63165783 Update dependencies 2021-03-20 13:03:31 +01:00
Ricki Hirner
9048af4af5 Update ical4android 2021-03-20 12:38:58 +01:00
Ricki Hirner
512754d014 Typo 2021-03-12 12:12:52 +01:00
Ricki Hirner
8fdf57ea28 Version bump to 3.3.10-beta2 2021-03-08 19:20:38 +01:00
Ricki Hirner
a1506e1514 Update Kotlin, ical4android 2021-03-08 19:20:13 +01:00
Ricki Hirner
3049f7304d Update AboutLibraries 2021-03-08 18:50:54 +01:00
Ricki Hirner
b1d230353e debug info: wording; update JUnit 2021-03-08 18:45:12 +01:00
Ricki Hirner
f9a6107f38 Debug info: allow sharing only after debug info is generated 2021-03-02 12:31:19 +01:00
Ricki Hirner
266490bf14 Update libraries 2021-03-02 11:53:01 +01:00
Ricki Hirner
c037b7e152 Version bump to 3.3.10-beta1 2021-02-12 17:59:28 +01:00
Ricki Hirner
83f257f07a Events: support URL as extended property; rewrite deprecated unknown properties to current format 2021-02-12 17:47:11 +01:00
Ricki Hirner
1d35f80afb Fetch translations from Transifex 2021-02-09 11:02:45 +01:00
Ricki Hirner
46b78c043b Version bump to 3.3.9 2021-02-09 11:00:27 +01:00
Ricki Hirner
0687b8599e Version bump to 3.3.9-beta4 2021-02-07 13:12:51 +01:00
Ricki Hirner
77bd44044e Fix foreground notification crash 2021-02-07 12:56:11 +01:00
Ricki Hirner
e95366adcc Fix translations 2021-02-07 12:01:26 +01:00
Ricki Hirner
a1d689eb1b Fetch translations from Transifex 2021-02-07 12:01:26 +01:00
Ricki Hirner
2d5b879b34 Update kotlin and cert4android 2021-02-07 12:01:26 +01:00
Ricki Hirner
7667ecb437 Version bump to 3.3.9-beta3 2021-02-01 14:58:25 +01:00
Ricki Hirner
6942ad9867 Debug info activity: remove string 2021-02-01 14:40:09 +01:00
Ricki Hirner
8aba0c80bc New option: keep in foreground (may help when the device prevents automatic synchronization) 2021-02-01 14:40:09 +01:00
Ricki Hirner
652b0ba92c Version bump to 3.3.9-beta2 2021-01-31 13:45:57 +01:00
Ricki Hirner
a92593ea77 Update okhttp 2021-01-31 13:45:41 +01:00
Ricki Hirner
3fb4e305ad Debug info: show number of events, contacts, tasks for each account (to know whether sync is working) 2021-01-31 13:45:40 +01:00
Ricki Hirner
e96f8a627e Version bump to 3.3.9-beta1 2021-01-16 14:18:11 +01:00
Ricki Hirner
eebd21a02e AccountActivity: don't show "Create addressbook/calendar" when there's no bind privilege on any homeset 2021-01-16 12:53:32 +01:00
Ricki Hirner
8323ea1872 AccountActivity: finish when account has been removed externally 2021-01-16 12:53:32 +01:00
Ricki Hirner
2f1ee9994d *DAV service detection: save all information of homesets again 2021-01-16 12:53:32 +01:00
Ricki Hirner
e157322a1f Correctly show dav4jvm in About/Libraries 2021-01-16 12:53:32 +01:00
Ricki Hirner
40a35ac7a0 Version bump to 3.3.8 2021-01-11 20:10:34 +01:00
Ricki Hirner
d0385ea8ce Fetch translations from Transifex 2021-01-11 20:08:37 +01:00
Ricki Hirner
4a89bb762e Catch some exceptions 2021-01-11 20:05:18 +01:00
Ricki Hirner
51c96a50c0 Update vcard4android 2021-01-06 10:25:51 +01:00
Ricki Hirner
16464ed025 Fetch translations from Transifex 2021-01-02 15:27:43 +01:00
Ricki Hirner
5567e79d2d Update libraries and dependencies 2021-01-02 15:11:45 +01:00
Ricki Hirner
ececb08bb6 Debug info: include interface name and DNS servers (because DNS-related problems are reported often) 2020-12-23 16:49:21 +01:00
Ricki Hirner
07fccd86d1 Version bump to 3.3.8-beta1 2020-12-14 19:12:18 +01:00
Ricki Hirner
4e870123ff Merge branch 'dev-3.x_DontShowUnspportedCollections-ose' into dev-3.x-ose 2020-12-14 19:06:31 +01:00
Patrick
e2e852c2e0 Exclude unsupported calendar collections
Excludes calendar collections that have both VEVENT and VTODO not supported from being displayed in the collections overview.
2020-12-14 19:04:31 +01:00
Ricki Hirner
46ee17f04b Make default sync interval a setting 2020-12-14 18:52:06 +01:00
Ricki Hirner
21fffe0b65 Update dependencies 2020-12-14 18:51:52 +01:00
Ricki Hirner
35a6ddb89c Bump version code 2020-11-28 14:32:48 +01:00
Ricki Hirner
be48945c03 Support resizeable activites (multi-window mode) 2020-11-28 14:26:38 +01:00
Ricki Hirner
1a7b1f9420 Update ical4android and version code 2020-11-28 13:38:25 +01:00
Ricki Hirner
9e1dc44048 Version bump to 3.3.7 2020-11-28 08:58:47 +01:00
Ricki Hirner
30348b40a4 Fetch translations from Transifex 2020-11-27 21:47:02 +01:00
Ricki Hirner
7af2b8bd00 Version bump to 3.3.7-beta3 2020-11-27 21:46:16 +01:00
Ricki Hirner
90c503fc16 add "show only personal" as setting 2020-11-27 21:45:28 +01:00
Ricki Hirner
6411dfef3a Update ical4android and vcard4android; add HTTP/2 ping every 45 secs to avoid cancellation 2020-11-26 11:39:18 +01:00
Ricki Hirner
6b84350df5 Minor rewriting 2020-11-24 12:14:34 +01:00
Ricki Hirner
f1d06a305f Fetch translations from Transifex 2020-11-23 11:48:42 +01:00
Ricki Hirner
38eb9693d2 Version bump to 3.3.7-beta2 2020-11-23 11:39:00 +01:00
Ricki Hirner
c7c547e391 Update ical4android, vcard4android 2020-11-23 00:31:34 +01:00
Ricki Hirner
0226414458 Save sync intervals and check/repair them at app start and reboot 2020-11-19 17:51:24 +01:00
Ricki Hirner
7a95bfdb94 Update AboutLibraries 2020-11-17 23:36:12 +01:00
Ricki Hirner
491763025b Merge branch 'dev-3.x_NoCertificatesFoundToast-ose' into dev-3.x-ose 2020-11-16 09:22:11 +01:00
Patrick
2d6b9eef4d Shows a toast when no certificate was found
Currently the certificate selection would popup und disappear immedeately. This quick fix would show a Toast for User Feedback
2020-11-16 09:20:30 +01:00
Ricki Hirner
0a06d0ea11 Account settings: always allow client certificate selection 2020-11-16 09:05:14 +01:00
Ricki Hirner
737af880bd Add migration for AppDatabase v8 2020-11-15 22:41:54 +01:00
Ricki Hirner
19474a1e17 Version bump to 3.3.7-beta1 2020-11-15 21:38:04 +01:00
Ricki Hirner
326267afce DAV:owner: resolve relative paths, only show in UI when it's set 2020-11-15 21:32:28 +01:00
Ricki Hirner
8ff0a192b8 Update submodules 2020-11-15 21:13:21 +01:00
Ricki Hirner
e9a62eddf5 DAV:owner small rewrite 2020-11-15 21:11:39 +01:00
Patrick Lang
ced1ed9abf Added Owner
- Extend Collection to store owner
- Show Owner in collection_properties.xml
2020-11-15 20:04:32 +00:00
Ricki Hirner
a1898888cb Fix tests 2020-11-15 20:47:31 +01:00
Ricki Hirner
7e5a7dff20 Add an account setting for "Show only personal collections" 2020-11-15 20:35:46 +01:00
Ricki Hirner
5c44332e69 Add account view option: show only personal collections 2020-11-15 20:35:46 +01:00
Ricki Hirner
4fb6e39148 Associate each collection to its homeset 2020-11-15 20:35:46 +01:00
Ricki Hirner
2c28ffad2e Update dependencies 2020-11-14 13:09:48 +01:00
Patrick
674455cc8c Merge branch 'dev-3.x-ose' into dev-3.x_RTLSupportEnabled-ose 2020-11-11 19:36:28 +01:00
Ricki Hirner
540d1ddb74 Merge branch 'infomaniak/android-infomaniak-sync-fix-string-localization' into dev-3.x-ose 2020-11-10 22:04:09 +01:00
Joris Bodin
fb4f870742 Extract string for remove auto reset permission 2020-11-10 22:03:46 +01:00
Ricki Hirner
1a8fca415d Update ez-vcard 2020-11-10 13:34:07 +01:00
Ricki Hirner
b69ff2380a Wifi permission activity: add "Allow all the time" string for Android 10 2020-11-07 12:31:12 +01:00
Ricki Hirner
260c5f928d Version bump to 3.3.6 2020-11-07 12:31:12 +01:00
Patrick
d199560add Merge branch 'dev-3.x-ose' into dev-3.x_RTLSupportEnabled-ose 2020-11-06 21:03:26 +01:00
Ricki Hirner
775a8abc60 cert4android: fetch translations from Transifex 2020-11-05 12:10:14 +01:00
Ricki Hirner
74f2bf69d6 Fetch translations from Transifex 2020-11-05 12:09:19 +01:00
Ricki Hirner
c583d92205 Version bump to 3.3.6-beta2 2020-11-05 12:06:36 +01:00
Ricki Hirner
64cb191c6d Try to work around problems with package visibility: use package instead of provider in visibility declaration 2020-11-05 12:06:22 +01:00
Ricki Hirner
b84ef186d8 Merge branch 'mbiebl/davx5-ose-share-icon-dark' into dev-3.x-ose 2020-11-03 13:14:48 +01:00
Ricki Hirner
bf08231bbd Don't sync Webcal-only calendars/tasks lists over CalDAV (may cause duplicate calendars)
(If a calendar is both CalDAV- and Webcal-capable, i.e. resourcetype is collection
and subscribed, it will be shown as CalDAV calendar.)
2020-11-03 12:58:48 +01:00
Patrick
907c1cb1ba Fixed layout gravity in login_credentials_fragment 2020-10-31 14:12:22 +01:00
Patrick
203fa7b1cd RTL Support enabled
+ Checked all Screens
+ checked drawables and mirrored if applicable
+ checked screens without translations to still fulfill RTL layout
2020-10-31 13:03:33 +01:00
Ricki Hirner
1973fc4f29 Add login layout ID for pre-launch tests; version bump to 3.3.6-beta1 2020-10-30 18:23:32 +01:00
Ricki Hirner
e6cc326166 Check whether WiFi permissions are declared 2020-10-30 12:52:42 +01:00
Ricki Hirner
1f47798cf7 Minor refactoring: About, styles 2020-10-29 20:19:23 +01:00
Ricki Hirner
1ed91ff11a Use Western digits where Western digits are required; show locale in debug info 2020-10-26 18:28:32 +01:00
Ricki Hirner
0782af6e91 cert4android: use MD5 fingerprint instead of CN as key to store custom certs 2020-10-25 20:26:08 +01:00
Ricki Hirner
99f59286c7 Really disable calendar sync for an account when there is no CalDAV service; update ez-vcard 2020-10-25 17:56:55 +01:00
Ricki Hirner
ecdf2607f9 Update ez-vcard 2020-10-25 15:53:40 +01:00
Ricki Hirner
c4440bfc4f Always enable verbose logs in debug builds; show SyncResult when a sync is complete 2020-10-25 14:19:05 +01:00
Ricki Hirner
96aa2fe3ce Don't ask for active location services on Android <9 / location permissions on Android 8.0 2020-10-25 10:28:29 +01:00
Ricki Hirner
ca9044cf76 Fetch translations from Transifex 2020-10-24 14:02:00 +02:00
Ricki Hirner
88bbb3c8bf Update ical4android; version bump to 3.3.5 2020-10-24 13:52:42 +02:00
Ricki Hirner
b920cdb6f8 Add ICSx5 to visible packages so that it can be detected under Android 11 2020-10-23 13:11:45 +02:00
Ricki Hirner
baedab5228 Account settings: make password a password field 2020-10-23 00:33:52 +02:00
Ricki Hirner
8e0d85615f When an account is created, open it immediately after setup 2020-10-22 18:21:50 +02:00
Ricki Hirner
dc1d5b6016 Don't require location permissions for SSID access on Android <8 2020-10-21 13:21:22 +02:00
Ricki Hirner
b75064ee3c Version bump to 3.3.4.1 2020-10-20 12:20:04 +02:00
Ricki Hirner
f39a0c2459 Fetch translations from Transifex 2020-10-20 12:19:25 +02:00
Ricki Hirner
93dd713c41 Sync algorithm: when uploading a locally modified resource results in 404/410, try as fresh upload again 2020-10-20 12:10:24 +02:00
Ricki Hirner
817e2e834d Fetch translations from Transifex 2020-10-19 23:17:03 +02:00
Ricki Hirner
5e071c7a61 Fix libraries; version bump to 3.3.4 2020-10-19 23:16:11 +02:00
Ricki Hirner
26a7c72ad7 Update ical4j to 3.0.20 2020-10-19 20:38:43 +02:00
Michael Biebl
9323464dd1 Use dark variant of share icon in Debug activity
We typically provide a dark variant for icons that are used on a light
background. The current share icon sticks out a bit, so add a dark
variant of that icon and use that in the Debug activity.
2020-10-19 16:14:10 +02:00
Ricki Hirner
244dd7e3e7 Version bump to 3.3.4-beta1 2020-10-18 21:20:47 +02:00
Ricki Hirner
f365570952 Advanced login: small fixes 2020-10-18 12:32:56 +02:00
Patrick Lang
4f1d546174 Login with advanced options
Adds a new radiobutton for login with advanced options
2020-10-17 18:58:08 +00:00
Michael Biebl
998da1bebf Typo fixes 2020-10-17 11:45:32 +02:00
Ricki Hirner
3e2bb92a15 F-Droid Changelog 2020-10-16 14:05:15 +02:00
Ricki Hirner
b77586e31c Fetch translations from Transifex 2020-10-16 13:06:27 +02:00
Ricki Hirner
204e0444f9 Version bump to 3.3.3 2020-10-16 13:02:38 +02:00
Ricki Hirner
ebed7e63c2 Nextcloud Login Flow v2: better error handling 2020-10-16 13:02:20 +02:00
Ricki Hirner
0d9c78ee07 Debug info: add Share action hint 2020-10-16 12:40:48 +02:00
Ricki Hirner
6666b28f31 Version bump to 3.3.3-beta4 2020-10-15 18:18:28 +02:00
Ricki Hirner
acbd93ce7e Update gradle plugin to 4.1; update dependencies 2020-10-15 18:08:39 +02:00
Ricki Hirner
61904434ff Fetch translations from Transifex 2020-10-14 13:26:49 +02:00
Ricki Hirner
33f5cf8099 Version bump to 3.3.3-beta3 2020-10-14 13:25:36 +02:00
Ricki Hirner
d52e94ebe8 Fix bug where OpenTasks permissions where hidden in UI when tasks.org was not installed 2020-10-13 20:34:09 +02:00
Ricki Hirner
72faa6a456 Update dependencies 2020-10-13 20:33:36 +02:00
Ricki Hirner
3204000411 Nextcloud: use Login Flow v2 for better browsing experience and to support things like Webauthn 2020-10-05 15:45:32 +02:00
Ricki Hirner
63572ad62a Debug info: show all permissions 2020-10-04 10:45:49 +02:00
Ricki Hirner
394e319959 Version bump to 3.3.3-beta2 2020-10-03 14:22:36 +02:00
Ricki Hirner
bd154a6315 Fetch translations from Transifex 2020-10-03 14:21:23 +02:00
Ricki Hirner
35b92f4469 Background location: use mandatory wording for explanation [https://support.google.com/googleplay/android-developer/answer/9799150?hl=en#prominent_disclosure] 2020-10-03 14:11:43 +02:00
Ricki Hirner
eaf5b37c01 Version bump to 3.3.3-beta1 2020-10-02 13:12:24 +02:00
Ricki Hirner
2656e7e58d Fetch translations from Transifex 2020-10-02 13:04:41 +02:00
Ricki Hirner
7e98563038 Improve logging notification/debug info; fix account settings UI WiFi permission info 2020-10-02 13:03:33 +02:00
Ricki Hirner
4a83df50b6 Error "Expected 207, got 200": show HTTP request/body in debug info 2020-10-01 12:23:12 +02:00
Ricki Hirner
cea038f29b Small compatibility fix/lint 2020-10-01 11:35:47 +02:00
Ricki Hirner
0bb1aa0243 Android 11: declare package visibility; adapt debug info 2020-10-01 11:05:17 +02:00
Ricki Hirner
de7ca2cf16 Refactor navigation from/to account settings activity 2020-10-01 11:05:17 +02:00
Ricki Hirner
f057b41c0f Android 11: handle new background location permissions for WiFi SSID restriction 2020-10-01 11:05:17 +02:00
Ricki Hirner
ab5f834ded Android 11: increase target SDK level; PermissionsFragment: ask to keep permissions on Android >= 11 2020-10-01 11:05:15 +02:00
Ricki Hirner
31c9f0f745 F-droid release notes 2020-09-29 15:57:48 +02:00
Ricki Hirner
d9f8185604 Version bump to 3.3.2 2020-09-29 15:23:53 +02:00
Ricki Hirner
da38fab28d Version bump to 3.3.2-beta5 2020-09-27 16:55:42 +02:00
Ricki Hirner
511ae6ad4e Fetch translations from Transifex 2020-09-27 16:55:12 +02:00
Ricki Hirner
1883bc8f64 Sync intervals: remove endless loop, add tests 2020-09-27 16:48:30 +02:00
Ricki Hirner
def9222148 Cache task provider in the UI 2020-09-25 12:46:41 +02:00
Ricki Hirner
9c2b81017f Collections fragment: use current task provider's status only 2020-09-25 12:46:41 +02:00
Ricki Hirner
8a8ae586c1 Version bump to 3.3.2-beta4 2020-09-21 11:14:22 +02:00
Ricki Hirner
517e1daea9 Fix tasks sync interval setting for tasks.org 2020-09-20 22:30:01 +02:00
Ricki Hirner
559665a481 Bump version to 3.3.2-beta3 2020-09-20 14:23:02 +02:00
Ricki Hirner
65f5f3cf62 Fetch translations from Transifex 2020-09-20 14:22:29 +02:00
Ricki Hirner
92bf8d55a1 Fix permissions UI 2020-09-20 14:21:05 +02:00
Ricki Hirner
093671a43d Dependencies; lint 2020-09-19 14:50:39 +02:00
Ricki Hirner
36263e0044 Fetch translations from Transifex 2020-09-18 16:54:19 +02:00
Ricki Hirner
1ea156750c Update cert4android/vcard4android, bump version to 3.3.2-beta2 2020-09-18 16:52:39 +02:00
Ricki Hirner
799eb05097 Small TasksWatcher fix; update ical4android and build tools 2020-09-17 19:38:43 +02:00
Ricki Hirner
1a92067262 Version bump to 3.3.2-beta1 2020-09-15 21:59:37 +02:00
Ricki Hirner
a9d652684e tasks.org support 2020-09-15 21:59:18 +02:00
Ricki Hirner
1cd17a88f7 Update strings 2020-09-14 10:15:40 +02:00
Ricki Hirner
5a9dc79595 Move translation pt-rBR to pt 2020-09-13 18:09:26 +02:00
Ricki Hirner
62a4420d73 improve HTTP requests in debug info; cert4android update 2020-09-09 21:49:45 +02:00
Ricki Hirner
ff8de74f21 Add F-Droid Changelog 2020-09-09 12:54:21 +02:00
Ricki Hirner
ba973a7320 Fetch translations from Transifex 2020-09-09 12:06:12 +02:00
Ricki Hirner
5bfc343629 Version bump to 3.3.1; minor dependency updates 2020-09-09 12:00:02 +02:00
Ricki Hirner
d6df81586b Fetch translations from Transifex 2020-08-31 12:43:17 +02:00
Ricki Hirner
c502834530 Version bump to 3.3.1-beta1 2020-08-31 12:41:53 +02:00
Ricki Hirner
638f9f9264 Adaptation for Google Contacts in Android 11: export address book authenticator so that Google Contacts shows the address book accounts 2020-08-31 12:41:34 +02:00
Ricki Hirner
ed38d38f20 Scripts etc. 2020-08-31 12:41:33 +02:00
Ricki Hirner
838855b9e8 Add F-Droid changelog for 3.3 2020-08-23 18:02:32 +02:00
Ricki Hirner
99502d827d Fetch translations from Transifex 2020-08-22 13:21:15 +02:00
Ricki Hirner
0ee9ed4999 Version bump to 3.3 2020-08-22 13:17:36 +02:00
Ricki Hirner
b2a6bddfa5 Update dependencies (including ical4android) 2020-08-22 13:17:04 +02:00
Ricki Hirner
6898e05b7f Version bump to 3.3-beta5 2020-08-15 13:36:17 +02:00
Ricki Hirner
83eca1ea3d Update app settings dynamically when they are changed 2020-08-15 12:37:58 +02:00
Ricki Hirner
4e42aa1375 Use plain-text debug info instead of HTML, again
* use UTF-8
* use plain-text tables
2020-08-15 12:19:58 +02:00
Ricki Hirner
949049f414 Fetch translations from Transifex 2020-08-12 12:12:11 +02:00
Ricki Hirner
5346095d66 Version bump to 3.3-beta4 2020-08-12 12:11:23 +02:00
Ricki Hirner
7358461ecc Add settings to debug info 2020-08-12 12:11:09 +02:00
Ricki Hirner
22c399e6fd Fixes for Android <25 2020-08-10 15:32:30 +02:00
Ricki Hirner
6b2828646e Version bump to 3.3-beta3 2020-08-10 15:09:36 +02:00
Ricki Hirner
6ce830442d Rewrite DebugInfoActivity 2020-08-10 15:09:15 +02:00
Ricki Hirner
0129f4bcf8 Fix Sync all shortcut 2020-08-10 15:09:14 +02:00
Ricki Hirner
d4942cbf14 Some lint 2020-08-07 12:23:50 +02:00
Ricki Hirner
f18a8cb9ab Update AppIntro 2020-08-07 11:44:32 +02:00
Ricki Hirner
f4194a7f95 Update dependencies 2020-08-07 11:44:21 +02:00
Ricki Hirner
a79ec19c6a Provide "Sync all accounts" option
* show sync status in accounts overview
* add "sync all accounts" in accounts overview
* add "sync all accounts" shortcut
2020-08-02 17:22:12 +02:00
Ricki Hirner
a14169d19d Bump version code 2020-08-02 17:14:36 +02:00
Ricki Hirner
28dc549744 Fix isSyncable problems
- set isSyncable(AddressBooks)=0 at account creation when there is no CardDAV service
- OpenTasksWatcher: enumerate real accounts and not services from DB -> isSyncable(tasks) will be set to 0 correctly for accounts without CalDAV service
- SyncAdapter base class: set autoInitialize to true, although it should not be needed regularly
2020-08-02 17:14:24 +02:00
Ricki Hirner
3069d79e8b Version bump to 3.2.1.4 2020-08-01 00:05:15 +02:00
Ricki Hirner
2e9f4c4a95 Update ical4android, vcard4android 2020-08-01 00:04:53 +02:00
Ricki Hirner
a833d002df Don't grey out WiFi SSID setting when data saver is active; update dependencies 2020-08-01 00:04:53 +02:00
Ricki Hirner
87a52cceef Sync interval settings: wait with UI update until settings have arrived in sync framework 2020-08-01 00:04:53 +02:00
Ricki Hirner
349c1beab5 Fetch translations from Transifex 2020-07-25 11:55:20 +02:00
Ricki Hirner
8835220116 Update vcard4android, version bump to 3.2.1.3 2020-07-25 11:50:04 +02:00
Ricki Hirner
6982304ea3 Nextcloud login flow: decode parameters in nc://login URL 2020-07-24 18:39:20 +02:00
831 changed files with 66648 additions and 21899 deletions

4
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,4 @@
# See https://docs.github.com/de/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# For combination with "Require review from code owners" for main-ose branch:
* @bitfireAT/app-dev

4
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
github: bitfireAT
liberapay: DAVx5
custom: 'https://www.davx5.com/donate'

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: DAVx⁵ Community Support
url: https://github.com/bitfireAT/davx5-ose/discussions
about: Ask and answer questions (including feature requests and bug reports) here.

View File

@@ -0,0 +1,44 @@
name: Qualified Bug Report
description: "For qualified bug reports. (Use Discussions if unsure.)"
type: bug
labels: ["bug"]
body:
- type: checkboxes
attributes:
label: Problem scope
description: Use Discussions if you're unsure which component (DAVx⁵, calendar app, server, …) causes your problem.
options:
- label: I'm sure that this is a DAVx⁵ problem.
required: true
- type: checkboxes
attributes:
label: App version
options:
- label: I'm using the latest available DAVx⁵ version.
required: true
- type: input
attributes:
label: Android version and device/firmware type
placeholder: "Android 13 (Samsung A32)"
- type: textarea
attributes:
label: Steps to reproduce
description: Provide detailed steps to reproduce the problem.
placeholder: |
1. Create DAVx⁵ account with Some Server (Version).
2. Sync Some Calendar.
3. SomeException appears.
- type: textarea
attributes:
label: Actual result
description: Describe what you DAVx⁵ currently does (and what is not expected).
placeholder: "Some Property in ICS file causes the whole synchronization to stop."
- type: textarea
attributes:
label: Expected result
description: Describe what you would expect DAVx⁵ to avoid/solve the problem.
placeholder: "Some Property in ICS file should be ignored even if faulty and sync should continue instead of showing an error."
- type: textarea
attributes:
label: Further info
description: Debug info, links to further information, …

View File

@@ -0,0 +1,20 @@
name: Qualified Feature Request
description: "For qualified feature requests. (Use Discussions if unsure.)"
type: feature
labels: ["enhancement"]
body:
- type: checkboxes
attributes:
label: Scope
description: Use this form only for features that have been discussed in Discussions or if you're a DAVx5 developer.
options:
- label: I'm sure that this feature request belongs here and not into Discussions.
required: true
- type: textarea
attributes:
label: Description
description: Describe the requested feature and why it is desired.
- type: textarea
attributes:
label: Further info
description: How this could be implemented, links to further information, …

27
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
version: 2
updates:
# Enable version updates for GitHub Actions
- package-ecosystem: "github-actions"
# Workflow files stored in the default location of `.github/workflows`
# You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "[CI] "
groups:
ci-actions:
patterns: ["*"]
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
groups:
app-dependencies:
patterns: ["*"]
ignore:
# dependencies without semantic versioning
- dependency-name: "com.github.bitfireat:cert4android"
- dependency-name: "com.github.bitfireat:dav4jvm"
- dependency-name: "com.github.bitfireat:synctools"

38
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,38 @@
Please delete this paragraph and other repeating text (like the examples) after reading and before submitting the PR.
The PR should be in _Draft_ state during development. As soon as it's finished, it should be marked as _Ready for review_ and a reviewer should be chosen.
See also: [Writing A Great Pull Request Description](https://www.pullrequest.com/blog/writing-a-great-pull-request-description/)
### Purpose
What this PR is intended to do and why this is desirable.
Example:
> Adds support for AAA in BBB, as requested by several people in issue #XX.
### Short description
A short description of the chosen approach to achieve the purpose.
Example:
> - Added authentication option _Some authentication_ to _some module_.
> - Added support for _Some authentication_ to _some content provider_.
> - Added UI support for _Some authentication_ in account settings.
Related information (links to Android docs and other resources that help to understand/review
the changes) can also be put here.
### Checklist
- [ ] The PR has a proper title, description and label.
- [ ] I have [self-reviewed the PR](https://patrickdinh.medium.com/review-your-own-pull-requests-5634cad10b7a).
- [ ] I have added documentation to complex functions and functions that can be used by other modules.
- [ ] I have added reasonable tests or consciously decided to not add tests.

20
.github/release.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
changelog:
exclude:
labels:
- ignore-for-release
categories:
- title: New features
labels:
- enhancement
- title: Bug fixes
labels:
- bug
- title: Refactoring
labels:
- refactoring
- title: Dependencies
labels:
- dependencies
- title: Other changes
labels:
- "*"

59
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: "CodeQL"
on:
push:
branches: [ main-ose ]
# pull_request:
# The branches below must be a subset of the branches above
# branches: [ main-ose ]
schedule:
- cron: '22 10 * * 1'
concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
steps:
- name: Checkout repository
uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
cache-read-only: true # gradle user home cache is generated by test jobs
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
#- name: Autobuild
# uses: github/codeql-action/autobuild@v2
- name: Build
run: ./gradlew --no-daemon app:compileOseDebugSource
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"

View File

@@ -0,0 +1,24 @@
name: Dependency Submission
on:
push:
branches: [ 'main-ose' ]
permissions:
contents: write
jobs:
dependency-submission:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
dependency-graph-exclude-configurations: '.*[Tt]est.* .*[cC]heck.*'

47
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: Create release
on:
push:
tags:
- v*
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
env:
prerelease: ${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }}
jobs:
build:
name: Create release
permissions:
contents: write
discussions: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v5
- name: Prepare keystore
run: echo ${{ secrets.android_keystore_base64 }} | base64 -d >$GITHUB_WORKSPACE/keystore.jks
- name: Build signed package
# Use build cache to speed up building of build variants, but clean caches from previous tests before
run: ./gradlew --build-cache --configuration-cache --no-daemon app:clean app:assembleRelease
env:
ANDROID_KEYSTORE: ${{ github.workspace }}/keystore.jks
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.android_keystore_password }}
ANDROID_KEY_ALIAS: ${{ secrets.android_key_alias }}
ANDROID_KEY_PASSWORD: ${{ secrets.android_key_password }}
- name: Create Github release
uses: softprops/action-gh-release@v2
with:
prerelease: ${{ env.prerelease }}
files: app/build/outputs/apk/ose/release/*.apk
fail_on_unmatched_files: true
generate_release_notes: true

133
.github/workflows/test-dev.yml vendored Normal file
View File

@@ -0,0 +1,133 @@
name: Development tests
on:
push:
branches:
- 'main-ose'
pull_request:
concurrency:
group: test-dev-${{ github.ref }}
cancel-in-progress: true
# We provide a remote gradle build cache. Take the settings from the secrets and enable
# configuration and build cache for all gradle jobs.
env:
GRADLE_BUILDCACHE_URL: ${{ secrets.gradle_buildcache_url }}
GRADLE_BUILDCACHE_USERNAME: ${{ secrets.gradle_buildcache_username }}
GRADLE_BUILDCACHE_PASSWORD: ${{ secrets.gradle_buildcache_password }}
GRADLE_OPTS: -Dorg.gradle.caching=true -Dorg.gradle.configuration-cache=true
jobs:
compile:
name: Compile
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
# See https://community.gradle.org/github-actions/docs/setup-gradle/ for more information
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
cache-read-only: false # allow branches to update their configuration cache
gradle-home-cache-excludes: caches/build-cache-1 # don't cache local build cache because we use a remote cache
- name: Cache Android environment
uses: actions/cache@v5
with:
path: ~/.config/.android # needs to be cached so that configuration cache can work
key: android-${{ hashFiles('app/build.gradle.kts') }}
- name: Compile
run: ./gradlew app:compileOseDebugSource
# Cache configurations for the other jobs
- name: Populate configuration cache
run: |
./gradlew --dry-run app:lintOseDebug
./gradlew --dry-run app:testOseDebugUnitTest
./gradlew --dry-run app:virtualOseDebugAndroidTest
unit_tests:
needs: compile
name: Lint and unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
cache-read-only: true
- name: Restore Android environment
uses: actions/cache/restore@v5
with:
path: ~/.config/.android
key: android-${{ hashFiles('app/build.gradle.kts') }}
- name: Lint checks
run: ./gradlew app:lintOseDebug
- name: Unit tests
run: ./gradlew app:testOseDebugUnitTest
instrumented_tests:
needs: compile
name: Instrumented tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v5
with:
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
cache-read-only: true
- name: Restore Android environment
uses: actions/cache/restore@v5
with:
path: ~/.config/.android
key: android-${{ hashFiles('app/build.gradle.kts') }}
# gradle and Android SDK often take more space than what is available on the default runner.
# We try to free a few GB here to make gradle-managed devices more reliable.
- name: Free some disk space
uses: jlumbroso/free-disk-space@main
with:
android: false # we need the Android SDK
large-packages: false # apt takes too long
swap-storage: false # gradle needs much memory
- name: Restore AVD
id: restore-avd
uses: actions/cache/restore@v5
with:
path: ~/.config/.android/avd # where AVD is stored
key: avd-${{ hashFiles('app/build.gradle.kts') }} # gradle-managed devices are defined there
# Enable virtualization for Android emulator
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Instrumented tests
run: ./gradlew app:virtualOseDebugAndroidTest
- name: Cache AVD
uses: actions/cache/save@v5
if: steps.restore-avd.outputs.cache-hit != 'true'
with:
path: ~/.config/.android/avd # where AVD is stored
key: avd-${{ hashFiles('app/build.gradle.kts') }} # gradle-managed devices are defined there

7
.gitignore vendored
View File

@@ -8,17 +8,14 @@
# Files for the Dalvik VM
*.dex
# Java class files
# Java/Kotlin
*.class
.kotlin/
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties

View File

@@ -1,31 +0,0 @@
image: registry.gitlab.com/bitfireat/docker-android-emulator:latest
before_script:
- git submodule update --init --recursive
- export GRADLE_USER_HOME=`pwd`/.gradle; chmod +x gradlew
cache:
paths:
- .gradle/
test:
tags:
- privileged
script:
- start-emulator.sh
- ./gradlew app:check app:connectedCheck
artifacts:
paths:
- app/build/outputs/lint-results-debug.html
- app/build/reports
- build/reports
pages:
script:
- ./gradlew app:dokka
- mkdir public && mv app/build/dokka public
artifacts:
paths:
- public
only:
- master-ose

9
.gitmodules vendored
View File

@@ -1,9 +0,0 @@
[submodule "ical4android"]
path = ical4android
url = https://gitlab.com/bitfireAT/ical4android.git
[submodule "vcard4android"]
path = vcard4android
url = https://gitlab.com/bitfireAT/vcard4android.git
[submodule "cert4android"]
path = cert4android
url = https://gitlab.com/bitfireAT/cert4android.git

9
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" />
<option name="RIGHT_MARGIN" value="180" />
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
.idea/copyright/LICENSE.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details." />
<option name="myName" value="LICENSE" />
</copyright>
</component>

3
.idea/copyright/profiles_settings.xml generated Normal file
View File

@@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="LICENSE" />
</component>

30
.tx/config Normal file
View File

@@ -0,0 +1,30 @@
[main]
host = https://www.transifex.com
lang_map = ar_SA: ar, en_GB: en-rGB, fa_IR: fa-rIR, fi_FI: fi, nb_NO: nb, pt_BR: pt-rBR, sk_SK: sk, sl_SI: sl, tr_TR: tr, zh_CN: zh, zh_TW: zh-rTW
[o:bitfireAT:p:davx5:r:app]
file_filter = app/src/main/res/values-<lang>/strings.xml
source_file = app/src/main/res/values/strings.xml
source_lang = en
type = ANDROID
minimum_perc = 20
resource_name = App strings (all flavors)
# Attention: fastlane directories are like "en-us", not "en-rUS"!
[o:bitfireAT:p:davx5:r:metadata-short-description]
file_filter = fastlane/metadata/android/<lang>/short_description.txt
source_file = fastlane/metadata/android/en-US/short_description.txt
source_lang = en
type = TXT
minimum_perc = 100
resource_name = Metadata: short description
[o:bitfireAT:p:davx5:r:metadata-full-description]
file_filter = fastlane/metadata/android/<lang>/full_description.txt
source_file = fastlane/metadata/android/en-US/full_description.txt
source_lang = en
type = TXT
minimum_perc = 100
resource_name = Metadata: full description

7
AUTHORS Normal file
View File

@@ -0,0 +1,7 @@
You can view the list of people who have contributed to the code base in the version control history:
https://github.com/bitfireAT/davx5-ose/graphs/contributors
Translators are not mentioned in the history explicitly.
The list of translators can be found in the About screen.
Every contribution is welcome. There are many other forms of contributing besides writing code!

View File

@@ -1,47 +1,117 @@
Contributing to DAVx⁵
=====================
**Thank you for your interest in contributing to DAVx⁵!**
Because you're reading this, you're probably interested in
contributing to the DAVx⁵ code. [Other ways to contribute:
see here.](https://www.davx5.com/donate#c306)
To contribute:
# Licensing
1. It's good idea to have a look at the [DAVx⁵ Roadmap](https://gitlab.com/bitfireAT/davx5-ose/wikis/Roadmap)
to see whether the change is already planned. Maybe there's even a link to a
corresponding forum thread there.
1. Determine which project the changes shall go to. There's
the DAVx⁵ main project (this repo), and the [related
libraries](README.md).
1. Please post to the [DAVx⁵ development forum](https://www.davx5.com/forums)
before doing actual work (unless you do it only for yourself, of course).
This will help to coordinate activities and you'll also get hints
about where to start and possible pitfalls.
1. Fork the repository.
1. Do the changes in your repository.
1. Submit a pull request to the original project.
1. Post in the forum again (to make sure the pull request is being notified).
All work in this repository is [licensed under the GPLv3](LICENSE).
We (bitfire.at, initial and main contributors) are also asking you to give us
permission to use your contribution for related non-open source projects
like [Managed DAVx⁵](https://www.davx5.com/organizations/managed-davx5).
If you send us a pull request, our CLA bot will ask you to sign the
Contributor's License Agreement so that we can use your contribution.
Questions, discussion
=====================
# Copyright
We're happy to see questions, discussions etc. in the
[DAVx⁵ development forum](https://www.davx5.com/forums)!
Make sure that every file that contains significant work (at least every code file)
starts with the copyright header:
```
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
```
You can set this in Android Studio:
1. Settings / Editor / Copyright / Copyright Profiles
2. Paste the text above (without the stars).
3. Set Formatting so that the preview exactly looks like above; one blank line after the block.
4. Set this copyright profile as the default profile for the project.
5. Apply copyright: right-click in file tree / Update copyright.
Licensing
=========
# Style guide
All code has to be licensed under the GPL.
Please adhere to the [Kotlin style guide](https://developer.android.com/kotlin/style-guide) and
the following hints to make the source code uniform.
We (bitfire.at, initial developers) are also asking you to double-license the
code so that we can also use it for related non-open source projects like
[Managed DAVx⁵](https://www.davx5.com/organizations/managed-davx5).
**Have a look at similar files and copy their style if you're not certain.**
Please find more about this in the Contributor's License Agreement (CLA)
we'll send to you if you want to contribute.
Sample file (pay attention to blank lines and other formatting):
```
<Copyright header, see above>
class MyClass(int arg1) : SuperClass() {
companion object {
const val CONSTANT_STRING = "Constant String";
fun staticMethod() { // Use static methods when you don't need the object context.
// …
}
}
var someProperty: String = "12345"
var someRelatedProperty: Int = 12345
init {
// constructor
}
/**
* Use KDoc to document important methods. Don't use it dogmatically, but writing proper documentation
* (not just the method name with spaces) helps you to re-think what the method shall really do.
*/
fun aFun1() { // Group methods by some logic (for instance, the order in which they will be called)
} // and alphabetically within a group.
fun anotherFun() {
// …
}
fun somethingCompletelyDifferent() { // two blank lines to separate groups
}
fun helperForSomethingCompletelyDifferent() {
someCall(arg1, arg2, arg3, arg4) // function calls: stick to one line unless it becomes confusing
}
class Model( // two blank lines before inner classes
someArgument: SomeLongClass, // arguments in multiple lines when they're too long for one line
anotherArgument: AnotherLongType,
thirdArgument: AnotherLongTypeName
) : ViewModel() {
fun abc() {
}
}
}
```
In general, use one blank line to separate things within one group of things, and two blank lines
to separate groups. In rare cases, when methods are tightly coupled and are only helpers for another
method, they may follow the calling method without separating blank lines.
## Tests
Test classes should be in the appropriate directory (see existing tests) and in the same package as the
tested class. Tests are usually be named like `methodToBeTested_Condition()`, see
[Test apps on Android](https://developer.android.com/training/testing/).
# Authors
If you make significant contributions, feel free to add yourself to the [AUTHORS file](AUTHORS).

View File

@@ -1,37 +1,45 @@
[![Website](https://img.shields.io/website?style=flat-square&up_color=%237cb342&url=https%3A%2F%2Fwww.davx5.com)](https://www.davx5.com/)
[![F-Droid](https://img.shields.io/f-droid/v/at.bitfire.davdroid?style=flat-square)](https://f-droid.org/packages/at.bitfire.davdroid/)
[![License](https://img.shields.io/github/license/bitfireAT/davx5-ose?style=flat-square)](https://github.com/bitfireAT/davx5-ose/blob/main/LICENSE)
[![Follow @davx5app@fosstodon.org](https://img.shields.io/mastodon/follow/109598783742737223?domain=https%3A%2F%2Ffosstodon.org&style=flat-square)](https://fosstodon.org/@davx5app)
[![Development tests](https://github.com/bitfireAT/davx5-ose/actions/workflows/test-dev.yml/badge.svg)](https://github.com/bitfireAT/davx5-ose/actions/workflows/test-dev.yml)
![DAVx⁵ logo](app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
DAVx⁵
========
Please see the [DAVx⁵ Web site](https://www.davx5.com) for
comprehensive information about DAVx⁵.
comprehensive information about DAVx⁵, including a list of services it has been tested with.
DAVx⁵ is licensed under the [GPLv3 License](LICENSE).
News and updates: [@davx5app](https://twitter.com/davx5app) on Twitter
News and updates:
Help, discussion, feature requests, bug reports and "issues": [DAVx⁵ forums](https://www.davx5.com/forums)
* [@davx5app@fosstodon.org](https://fosstodon.org/@davx5app) on Mastodon
**If you want to support DAVx⁵, please consider [donating to DAVx⁵](https://www.davx5.com/donate)
or [purchasing it](https://www.davx5.com/download).**
Generated KDoc: https://bitfireAT.gitlab.io/davx5-ose/dokka/app/
**Help, feature requests, bug reports: [DAVx⁵ discussions](https://github.com/bitfireAT/davx5-ose/discussions)**
Parts of DAVx⁵ have been outsourced into these libraries:
* [cert4android](https://gitlab.com/bitfireAT/cert4android) custom certificate management
* [dav4jvm](https://gitlab.com/bitfireAT/dav4jvm) WebDAV/CalDav/CardDAV framework
* [ical4android](https://gitlab.com/bitfireAT/ical4android) iCalendar processing and Calendar Provider access
* [vcard4android](https://gitlab.com/bitfireAT/vcard4android) vCard processing and Contacts Provider access
* [cert4android](https://github.com/bitfireAT/cert4android) custom certificate management
* [dav4jvm](https://github.com/bitfireAT/dav4jvm) WebDAV/CalDav/CardDAV framework
* [synctools](https://github.com/bitfireAT/synctools) iCalendar/vCard/Tasks processing and content provider access
**If you want to support DAVx⁵, please consider [donating to DAVx⁵](https://www.davx5.com/donate)
or [purchasing it](https://www.davx5.com/download).**
USED THIRD-PARTY LIBRARIES
==========================
Those libraries are used by DAVx⁵ (alphabetically):
The most important libraries which are used by DAVx⁵ (alphabetically):
* [Color Picker](https://github.com/jaredrummler/ColorPicker) [Apache License, Version 2.0](https://github.com/jaredrummler/ColorPicker/LICENSE)
* [dnsjava](http://www.xbill.org/dnsjava/) [BSD License](http://www.xbill.org/dnsjava/dnsjava-current/LICENSE)
* [ez-vcard](https://github.com/mangstadt/ez-vcard) [New BSD License](http://opensource.org/licenses/BSD-3-Clause)
* [iCal4j](https://github.com/ical4j/ical4j) [New BSD License](http://sourceforge.net/p/ical4j/ical4j/ci/default/tree/LICENSE)
* [dnsjava](https://github.com/dnsjava/dnsjava) [BSD License](https://github.com/dnsjava/dnsjava/blob/master/LICENSE)
* [ez-vcard](https://github.com/mangstadt/ez-vcard) [New BSD License](https://github.com/mangstadt/ez-vcard/blob/master/LICENSE)
* [iCal4j](https://github.com/ical4j/ical4j) [New BSD License](https://github.com/ical4j/ical4j/blob/develop/LICENSE.txt)
* [okhttp](https://square.github.io/okhttp) [Apache License, Version 2.0](https://square.github.io/okhttp/#license)
See _About / Libraries_ in the app for all used libraries and their licenses.

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Please report security vulnerabilities using our [secure support form](https://www.davx5.com/support) or via email to support-en@davx5.com.

View File

@@ -1,149 +0,0 @@
/*
* Copyright (c) Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
apply plugin: 'com.android.application'
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'org.jetbrains.dokka'
android {
compileSdkVersion 29
buildToolsVersion '29.0.2'
defaultConfig {
applicationId "at.bitfire.davdroid"
versionCode 302010201
buildConfigField "long", "buildTime", System.currentTimeMillis() + "L"
minSdkVersion 21 // Android 5.0
targetSdkVersion 29 // Android 10.0
buildConfigField "String", "userAgent", "\"DAVx5\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
// enable because ical4android requires desugaring
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures.dataBinding = true
flavorDimensions "distribution"
productFlavors {
standard {
versionName "3.2.1.2-ose"
}
}
buildTypes {
debug {
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules-release.pro'
shrinkResources true
}
}
lintOptions {
disable 'GoogleAppIndexingWarning' // we don't need Google indexing, thanks
disable 'ImpliedQuantity', 'MissingQuantity' // quantities from Transifex may vary
disable 'MissingTranslation', 'ExtraTranslation' // translations from Transifex are not always up to date
disable 'RtlEnabled', 'RtlHardcoded' // RTL not supported yet
disable 'Typos'
}
dokka.configuration {
sourceLink {
url = "https://gitlab.com/bitfireAT/davx5-ose/tree/master-ose/"
lineSuffix = "#L"
}
jdkVersion = 8
externalDocumentationLink {
url = new URL("https://bitfireat.gitlab.io/cert4android/dokka/cert4android/")
packageListUrl = new URL("https://bitfireat.gitlab.io/cert4android/dokka/cert4android/package-list")
}
externalDocumentationLink {
url = new URL("https://bitfireat.gitlab.io/dav4jvm/dokka/dav4jvm/")
packageListUrl = new URL("https://bitfireat.gitlab.io/dav4jvm/dokka/dav4jvm/package-list")
}
externalDocumentationLink {
url = new URL("https://bitfireat.gitlab.io/ical4android/dokka/ical4android/")
packageListUrl = new URL("https://bitfireat.gitlab.io/ical4android/dokka/ical4android/package-list")
}
externalDocumentationLink {
url = new URL("https://bitfireat.gitlab.io/vcard4android/dokka/vcard4android/")
packageListUrl = new URL("https://bitfireat.gitlab.io/vcard4android/dokka/vcard4android/package-list")
}
}
}
dependencies {
implementation project(':cert4android')
implementation project(':ical4android')
implementation project(':vcard4android')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9'
implementation 'androidx.appcompat:appcompat:1.2.0-rc01'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta8'
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.fragment:fragment-ktx:1.2.5'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android:flexbox:1.1.0'
implementation 'com.google.android.material:material:1.2.0-beta01'
def room_version = '2.2.5'
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation "com.github.AppIntro:AppIntro:${versions.appIntro}"
implementation "com.gitlab.bitfireAT:dav4jvm:${versions.dav4jvm}"
implementation "com.mikepenz:aboutlibraries:${versions.aboutLibraries}"
implementation "com.squareup.okhttp3:okhttp:${versions.okhttp}"
implementation "com.squareup.okhttp3:okhttp-brotli:${versions.okhttp}"
implementation "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}"
implementation 'commons-io:commons-io:2.6'
//noinspection GradleDependency - dnsjava 3+ needs Java 8/Android 7
implementation 'dnsjava:dnsjava:2.1.9'
implementation 'org.apache.commons:commons-collections4:4.4'
//noinspection GradleDependency - commons-lang 3.10+ needs Java 8/Android 7
implementation 'org.apache.commons:commons-lang3:3.9'
implementation 'org.apache.commons:commons-text:1.8'
// for tests
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'junit:junit:4.13'
androidTestImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}"
testImplementation 'junit:junit:4.13'
testImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}"
}

233
app/build.gradle.kts Normal file
View File

@@ -0,0 +1,233 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.hilt)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.mikepenz.aboutLibraries.android)
}
// Android configuration
android {
compileSdk = 36
defaultConfig {
applicationId = "at.bitfire.davdroid"
versionCode = 405080000
versionName = "4.5.8-alpha.1"
base.archivesName = "davx5-ose-$versionName"
minSdk = 24 // Android 7.0
targetSdk = 36 // Android 16
// whether the build supports and allows to use custom certificates
buildConfigField("boolean", "allowCustomCerts", "true")
testInstrumentationRunner = "at.bitfire.davdroid.HiltTestRunner"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
compileOptions {
// required for
// - dnsjava 3.x: java.nio.file.Path
// - ical4android: time API
isCoreLibraryDesugaringEnabled = true
}
buildFeatures {
buildConfig = true
compose = true
}
// Java namespace for our classes (not to be confused with Android package ID)
namespace = "at.bitfire.davdroid"
flavorDimensions += "distribution"
productFlavors {
create("ose") {
dimension = "distribution"
versionNameSuffix = "-ose"
}
}
sourceSets {
getByName("androidTest") {
assets.srcDir("$projectDir/schemas")
}
}
signingConfigs {
create("bitfire") {
storeFile = file(System.getenv("ANDROID_KEYSTORE") ?: "/dev/null")
storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD")
keyAlias = System.getenv("ANDROID_KEY_ALIAS")
keyPassword = System.getenv("ANDROID_KEY_PASSWORD")
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules-release.pro")
isShrinkResources = true
signingConfig = signingConfigs.findByName("bitfire")
}
}
lint {
disable += arrayOf("GoogleAppIndexingWarning", "ImpliedQuantity", "MissingQuantity", "MissingTranslation", "ExtraTranslation", "RtlEnabled", "RtlHardcoded", "Typos")
}
androidResources {
generateLocaleConfig = true
}
packaging {
resources {
// multiple (test) dependencies have LICENSE files at same location
merges += arrayOf("META-INF/LICENSE*")
}
}
@Suppress("UnstableApiUsage")
testOptions {
managedDevices {
localDevices {
create("virtual") {
device = "Pixel 3"
// TBD: API level 35 and higher causes network tests to fail sometimes, see https://github.com/bitfireAT/davx5-ose/issues/1525
// Suspected reason: https://developer.android.com/about/versions/15/behavior-changes-all#background-network-access
apiLevel = 34
systemImageSource = "aosp-atd"
}
}
}
}
}
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
aboutLibraries {
export {
// exclude timestamps for reproducible builds [https://github.com/bitfireAT/davx5-ose/issues/994]
excludeFields.add("generated")
}
}
dependencies {
// core
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines)
coreLibraryDesugaring(libs.android.desugaring)
// Hilt
implementation(libs.hilt.android.base)
ksp(libs.androidx.hilt.compiler)
ksp(libs.hilt.android.compiler)
// support libs
implementation(libs.androidx.activityCompose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.browser)
implementation(libs.androidx.core)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.hilt.work)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel.base)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.paging)
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.preference)
implementation(libs.androidx.security)
implementation(libs.androidx.work.base)
// Jetpack Compose
implementation(libs.compose.accompanist.permissions)
implementation(platform(libs.compose.bom))
implementation(libs.compose.material3)
implementation(libs.compose.materialIconsExtended)
debugImplementation(libs.compose.ui.tooling)
implementation(libs.compose.ui.toolingPreview)
// Glance Widgets
implementation(libs.glance.base)
implementation(libs.glance.material)
// Jetpack Room
implementation(libs.room.runtime)
implementation(libs.room.base)
implementation(libs.room.paging)
ksp(libs.room.compiler)
// own libraries
implementation(libs.bitfire.cert4android)
implementation(libs.bitfire.dav4jvm) {
exclude(group="junit")
exclude(group="org.ogce", module="xpp3") // Android has its own XmlPullParser implementation
}
implementation(libs.bitfire.synctools) {
exclude(group="androidx.test") // synctools declares test rules, but we don't want them in non-test code
exclude(group = "junit")
}
// third-party libs
implementation(libs.conscrypt)
implementation(libs.dnsjava)
implementation(libs.guava)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.okhttp)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.mikepenz.aboutLibraries.m3)
implementation(libs.okhttp.base)
implementation(libs.okhttp.brotli)
implementation(libs.okhttp.logging)
implementation(libs.openid.appauth)
implementation(libs.unifiedpush) {
// UnifiedPush connector seems to be using a workaround by importing this library.
// Will be removed after https://github.com/tink-crypto/tink-java-apps/pull/5 is merged.
// See: https://codeberg.org/UnifiedPush/android-connector/src/commit/28cb0d622ed0a972996041ab9cc85b701abc48c6/connector/build.gradle#L56-L59
exclude(group = "com.google.crypto.tink", module = "tink")
}
implementation(libs.unifiedpush.fcm)
// force some versions for compatibility with our minSdk level (see version catalog for details)
implementation(libs.commons.codec)
implementation(libs.commons.lang)
// for tests
androidTestImplementation(libs.androidx.arch.core.testing)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.work.testing)
androidTestImplementation(libs.hilt.android.testing)
androidTestImplementation(libs.junit)
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.mockk.android)
androidTestImplementation(libs.okhttp.mockwebserver)
androidTestImplementation(libs.room.testing)
testImplementation(libs.bitfire.dav4jvm)
testImplementation(libs.junit)
testImplementation(libs.mockk)
testImplementation(libs.okhttp.mockwebserver)
testImplementation(libs.robolectric)
}

View File

@@ -2,27 +2,30 @@
# R8 usage for DAVx⁵:
# shrinking yes (only in release builds)
# optimization yes (on by R8 defaults)
# full-mode no (see gradle.properties)
# obfuscation no (open-source)
-dontobfuscate
-printusage build/reports/r8-usage.txt
# keep rules
-keep class at.bitfire.** { *; } # all DAVx5 code is required
-keep class org.xmlpull.** { *; }
# Additional rules which are now required since missing classes can't be ignored in R8 anymore.
# [https://developer.android.com/build/releases/past-releases/agp-7-0-0-release-notes#r8-missing-class-warning]
-dontwarn org.xmlpull.**
# dnsjava
-dontwarn com.sun.jna.**
-dontwarn lombok.**
-dontwarn javax.naming.NamingException
-dontwarn javax.naming.directory.**
-dontwarn sun.net.spi.nameservice.NameService
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor
-dontwarn org.xbill.DNS.spi.DnsjavaInetAddressResolverProvider
# okhttp
-keepclassmembers class okhttp3.internal.Util.** { *; }
# ez-vcard: keep all vCard properties/parameters (used via reflection)
-keep class ezvcard.io.scribe.** { *; }
-keep class ezvcard.property.** { *; }
-keep class ezvcard.parameter.** { *; }
# ical4j: keep all iCalendar properties/parameters (used via reflection)
-keep class net.fortuna.ical4j.** { *; }
# DAVx + libs
-keep class at.bitfire.** { *; } # all DAVx code is required
# we use enum classes (https://www.guardsquare.com/en/products/proguard/manual/examples#enumerations)
-keepclassmembers,allowoptimization enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# https://github.com/bitfireAT/davx5/issues/711 / https://github.com/square/okhttp/issues/8574
-keep class okhttp3.internal.idn.IdnaMappingTable { *; }
-keep class okhttp3.internal.idn.IdnaMappingTableInstanceKt{ *; }

View File

@@ -0,0 +1,398 @@
{
"formatVersion": 1,
"database": {
"version": 10,
"identityHash": "6fcabe50cbd00a4215dbe536a565dd2a",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `owner` TEXT, `color` INTEGER, `timezone` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "owner",
"columnName": "owner",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6fcabe50cbd00a4215dbe536a565dd2a')"
]
}
}

View File

@@ -0,0 +1,536 @@
{
"formatVersion": 1,
"database": {
"version": 11,
"identityHash": "223aa7f0fd53730921ca212a663585d8",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `owner` TEXT, `color` INTEGER, `timezone` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "owner",
"columnName": "owner",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_document",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mountId` INTEGER NOT NULL, `parentId` INTEGER, `name` TEXT NOT NULL, `isDirectory` INTEGER NOT NULL, `displayName` TEXT, `mimeType` TEXT, `eTag` TEXT, `lastModified` INTEGER, `size` INTEGER, `mayBind` INTEGER, `mayUnbind` INTEGER, `mayWriteContent` INTEGER, `quotaAvailable` INTEGER, `quotaUsed` INTEGER, FOREIGN KEY(`mountId`) REFERENCES `webdav_mount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `webdav_document`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mountId",
"columnName": "mountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isDirectory",
"columnName": "isDirectory",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "eTag",
"columnName": "eTag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastModified",
"columnName": "lastModified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayBind",
"columnName": "mayBind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayUnbind",
"columnName": "mayUnbind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayWriteContent",
"columnName": "mayWriteContent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaAvailable",
"columnName": "quotaAvailable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaUsed",
"columnName": "quotaUsed",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_webdav_document_mountId_parentId_name",
"unique": true,
"columnNames": [
"mountId",
"parentId",
"name"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_webdav_document_mountId_parentId_name` ON `${TABLE_NAME}` (`mountId`, `parentId`, `name`)"
}
],
"foreignKeys": [
{
"table": "webdav_mount",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"mountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "webdav_document",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"parentId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '223aa7f0fd53730921ca212a663585d8')"
]
}
}

View File

@@ -0,0 +1,615 @@
{
"formatVersion": 1,
"database": {
"version": 12,
"identityHash": "67fafceecee2d97cac6a62d46fa2c3e2",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `ownerId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `color` INTEGER, `timezone` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`ownerId`) REFERENCES `principal`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
},
{
"table": "principal",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "principal",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `url` TEXT NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_principal_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_principal_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_document",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mountId` INTEGER NOT NULL, `parentId` INTEGER, `name` TEXT NOT NULL, `isDirectory` INTEGER NOT NULL, `displayName` TEXT, `mimeType` TEXT, `eTag` TEXT, `lastModified` INTEGER, `size` INTEGER, `mayBind` INTEGER, `mayUnbind` INTEGER, `mayWriteContent` INTEGER, `quotaAvailable` INTEGER, `quotaUsed` INTEGER, FOREIGN KEY(`mountId`) REFERENCES `webdav_mount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `webdav_document`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mountId",
"columnName": "mountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isDirectory",
"columnName": "isDirectory",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "eTag",
"columnName": "eTag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastModified",
"columnName": "lastModified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayBind",
"columnName": "mayBind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayUnbind",
"columnName": "mayUnbind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayWriteContent",
"columnName": "mayWriteContent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaAvailable",
"columnName": "quotaAvailable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaUsed",
"columnName": "quotaUsed",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_webdav_document_mountId_parentId_name",
"unique": true,
"columnNames": [
"mountId",
"parentId",
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_webdav_document_mountId_parentId_name` ON `${TABLE_NAME}` (`mountId`, `parentId`, `name`)"
}
],
"foreignKeys": [
{
"table": "webdav_mount",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"mountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "webdav_document",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"parentId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '67fafceecee2d97cac6a62d46fa2c3e2')"
]
}
}

View File

@@ -0,0 +1,640 @@
{
"formatVersion": 1,
"database": {
"version": 13,
"identityHash": "0a6a9705ff471acd766ab96e3edf8ac3",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `ownerId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `color` INTEGER, `timezone` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, `pushTopic` TEXT, `supportsWebPush` INTEGER NOT NULL DEFAULT 0, `pushSubscription` TEXT, `pushSubscriptionCreated` INTEGER, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`ownerId`) REFERENCES `principal`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pushTopic",
"columnName": "pushTopic",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsWebPush",
"columnName": "supportsWebPush",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "pushSubscription",
"columnName": "pushSubscription",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "pushSubscriptionCreated",
"columnName": "pushSubscriptionCreated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
},
{
"table": "principal",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "principal",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `url` TEXT NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_principal_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_principal_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_document",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mountId` INTEGER NOT NULL, `parentId` INTEGER, `name` TEXT NOT NULL, `isDirectory` INTEGER NOT NULL, `displayName` TEXT, `mimeType` TEXT, `eTag` TEXT, `lastModified` INTEGER, `size` INTEGER, `mayBind` INTEGER, `mayUnbind` INTEGER, `mayWriteContent` INTEGER, `quotaAvailable` INTEGER, `quotaUsed` INTEGER, FOREIGN KEY(`mountId`) REFERENCES `webdav_mount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `webdav_document`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mountId",
"columnName": "mountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isDirectory",
"columnName": "isDirectory",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "eTag",
"columnName": "eTag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastModified",
"columnName": "lastModified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayBind",
"columnName": "mayBind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayUnbind",
"columnName": "mayUnbind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayWriteContent",
"columnName": "mayWriteContent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaAvailable",
"columnName": "quotaAvailable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaUsed",
"columnName": "quotaUsed",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_webdav_document_mountId_parentId_name",
"unique": true,
"columnNames": [
"mountId",
"parentId",
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_webdav_document_mountId_parentId_name` ON `${TABLE_NAME}` (`mountId`, `parentId`, `name`)"
}
],
"foreignKeys": [
{
"table": "webdav_mount",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"mountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "webdav_document",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"parentId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0a6a9705ff471acd766ab96e3edf8ac3')"
]
}
}

View File

@@ -0,0 +1,669 @@
{
"formatVersion": 1,
"database": {
"version": 14,
"identityHash": "9a0eb47f27473eab254db568081a4585",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `ownerId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `color` INTEGER, `timezone` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, `pushTopic` TEXT, `supportsWebPush` INTEGER NOT NULL DEFAULT 0, `pushSubscription` TEXT, `pushSubscriptionCreated` INTEGER, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`ownerId`) REFERENCES `principal`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pushTopic",
"columnName": "pushTopic",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsWebPush",
"columnName": "supportsWebPush",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "pushSubscription",
"columnName": "pushSubscription",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "pushSubscriptionCreated",
"columnName": "pushSubscriptionCreated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_ownerId_type",
"unique": false,
"columnNames": [
"ownerId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_ownerId_type` ON `${TABLE_NAME}` (`ownerId`, `type`)"
},
{
"name": "index_collection_pushTopic_type",
"unique": false,
"columnNames": [
"pushTopic",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_pushTopic_type` ON `${TABLE_NAME}` (`pushTopic`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
},
{
"table": "principal",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "principal",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `url` TEXT NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_principal_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_principal_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_document",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mountId` INTEGER NOT NULL, `parentId` INTEGER, `name` TEXT NOT NULL, `isDirectory` INTEGER NOT NULL, `displayName` TEXT, `mimeType` TEXT, `eTag` TEXT, `lastModified` INTEGER, `size` INTEGER, `mayBind` INTEGER, `mayUnbind` INTEGER, `mayWriteContent` INTEGER, `quotaAvailable` INTEGER, `quotaUsed` INTEGER, FOREIGN KEY(`mountId`) REFERENCES `webdav_mount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `webdav_document`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mountId",
"columnName": "mountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isDirectory",
"columnName": "isDirectory",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "eTag",
"columnName": "eTag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastModified",
"columnName": "lastModified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayBind",
"columnName": "mayBind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayUnbind",
"columnName": "mayUnbind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayWriteContent",
"columnName": "mayWriteContent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaAvailable",
"columnName": "quotaAvailable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaUsed",
"columnName": "quotaUsed",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_webdav_document_mountId_parentId_name",
"unique": true,
"columnNames": [
"mountId",
"parentId",
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_webdav_document_mountId_parentId_name` ON `${TABLE_NAME}` (`mountId`, `parentId`, `name`)"
},
{
"name": "index_webdav_document_parentId",
"unique": false,
"columnNames": [
"parentId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_webdav_document_parentId` ON `${TABLE_NAME}` (`parentId`)"
}
],
"foreignKeys": [
{
"table": "webdav_mount",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"mountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "webdav_document",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"parentId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9a0eb47f27473eab254db568081a4585')"
]
}
}

View File

@@ -0,0 +1,675 @@
{
"formatVersion": 1,
"database": {
"version": 15,
"identityHash": "ab1cb6057d8e050f6648bea46ae0943d",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `ownerId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `color` INTEGER, `timezone` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, `pushTopic` TEXT, `supportsWebPush` INTEGER NOT NULL DEFAULT 0, `pushSubscription` TEXT, `pushSubscriptionExpires` INTEGER, `pushSubscriptionCreated` INTEGER, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`ownerId`) REFERENCES `principal`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pushTopic",
"columnName": "pushTopic",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsWebPush",
"columnName": "supportsWebPush",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "pushSubscription",
"columnName": "pushSubscription",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "pushSubscriptionExpires",
"columnName": "pushSubscriptionExpires",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "pushSubscriptionCreated",
"columnName": "pushSubscriptionCreated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_ownerId_type",
"unique": false,
"columnNames": [
"ownerId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_ownerId_type` ON `${TABLE_NAME}` (`ownerId`, `type`)"
},
{
"name": "index_collection_pushTopic_type",
"unique": false,
"columnNames": [
"pushTopic",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_pushTopic_type` ON `${TABLE_NAME}` (`pushTopic`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
},
{
"table": "principal",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "principal",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `url` TEXT NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_principal_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_principal_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_document",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mountId` INTEGER NOT NULL, `parentId` INTEGER, `name` TEXT NOT NULL, `isDirectory` INTEGER NOT NULL, `displayName` TEXT, `mimeType` TEXT, `eTag` TEXT, `lastModified` INTEGER, `size` INTEGER, `mayBind` INTEGER, `mayUnbind` INTEGER, `mayWriteContent` INTEGER, `quotaAvailable` INTEGER, `quotaUsed` INTEGER, FOREIGN KEY(`mountId`) REFERENCES `webdav_mount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `webdav_document`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mountId",
"columnName": "mountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isDirectory",
"columnName": "isDirectory",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "eTag",
"columnName": "eTag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastModified",
"columnName": "lastModified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayBind",
"columnName": "mayBind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayUnbind",
"columnName": "mayUnbind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayWriteContent",
"columnName": "mayWriteContent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaAvailable",
"columnName": "quotaAvailable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaUsed",
"columnName": "quotaUsed",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_webdav_document_mountId_parentId_name",
"unique": true,
"columnNames": [
"mountId",
"parentId",
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_webdav_document_mountId_parentId_name` ON `${TABLE_NAME}` (`mountId`, `parentId`, `name`)"
},
{
"name": "index_webdav_document_parentId",
"unique": false,
"columnNames": [
"parentId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_webdav_document_parentId` ON `${TABLE_NAME}` (`parentId`)"
}
],
"foreignKeys": [
{
"table": "webdav_mount",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"mountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "webdav_document",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"parentId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab1cb6057d8e050f6648bea46ae0943d')"
]
}
}

View File

@@ -0,0 +1,675 @@
{
"formatVersion": 1,
"database": {
"version": 16,
"identityHash": "2ff7560d957e03a78b4b7de88aa9593b",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `ownerId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `color` INTEGER, `timezoneId` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, `pushTopic` TEXT, `supportsWebPush` INTEGER NOT NULL DEFAULT 0, `pushSubscription` TEXT, `pushSubscriptionExpires` INTEGER, `pushSubscriptionCreated` INTEGER, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`ownerId`) REFERENCES `principal`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezoneId",
"columnName": "timezoneId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pushTopic",
"columnName": "pushTopic",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsWebPush",
"columnName": "supportsWebPush",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "pushSubscription",
"columnName": "pushSubscription",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "pushSubscriptionExpires",
"columnName": "pushSubscriptionExpires",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "pushSubscriptionCreated",
"columnName": "pushSubscriptionCreated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_ownerId_type",
"unique": false,
"columnNames": [
"ownerId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_ownerId_type` ON `${TABLE_NAME}` (`ownerId`, `type`)"
},
{
"name": "index_collection_pushTopic_type",
"unique": false,
"columnNames": [
"pushTopic",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_pushTopic_type` ON `${TABLE_NAME}` (`pushTopic`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
},
{
"table": "principal",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "principal",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `url` TEXT NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_principal_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_principal_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_document",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mountId` INTEGER NOT NULL, `parentId` INTEGER, `name` TEXT NOT NULL, `isDirectory` INTEGER NOT NULL, `displayName` TEXT, `mimeType` TEXT, `eTag` TEXT, `lastModified` INTEGER, `size` INTEGER, `mayBind` INTEGER, `mayUnbind` INTEGER, `mayWriteContent` INTEGER, `quotaAvailable` INTEGER, `quotaUsed` INTEGER, FOREIGN KEY(`mountId`) REFERENCES `webdav_mount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `webdav_document`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mountId",
"columnName": "mountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isDirectory",
"columnName": "isDirectory",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "eTag",
"columnName": "eTag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastModified",
"columnName": "lastModified",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayBind",
"columnName": "mayBind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayUnbind",
"columnName": "mayUnbind",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mayWriteContent",
"columnName": "mayWriteContent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaAvailable",
"columnName": "quotaAvailable",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "quotaUsed",
"columnName": "quotaUsed",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_webdav_document_mountId_parentId_name",
"unique": true,
"columnNames": [
"mountId",
"parentId",
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_webdav_document_mountId_parentId_name` ON `${TABLE_NAME}` (`mountId`, `parentId`, `name`)"
},
{
"name": "index_webdav_document_parentId",
"unique": false,
"columnNames": [
"parentId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_webdav_document_parentId` ON `${TABLE_NAME}` (`parentId`)"
}
],
"foreignKeys": [
{
"table": "webdav_mount",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"mountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "webdav_document",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"parentId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2ff7560d957e03a78b4b7de88aa9593b')"
]
}
}

View File

@@ -0,0 +1,648 @@
{
"formatVersion": 1,
"database": {
"version": 17,
"identityHash": "cd15d368408570cc2e57252816869de2",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
]
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `ownerId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `color` INTEGER, `timezoneId` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, `pushTopic` TEXT, `supportsWebPush` INTEGER NOT NULL DEFAULT 0, `pushVapidKey` TEXT, `pushSubscription` TEXT, `pushSubscriptionExpires` INTEGER, `pushSubscriptionCreated` INTEGER, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`ownerId`) REFERENCES `principal`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER"
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "INTEGER"
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT"
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT"
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER"
},
{
"fieldPath": "timezoneId",
"columnName": "timezoneId",
"affinity": "TEXT"
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER"
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER"
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER"
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT"
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pushTopic",
"columnName": "pushTopic",
"affinity": "TEXT"
},
{
"fieldPath": "supportsWebPush",
"columnName": "supportsWebPush",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "pushVapidKey",
"columnName": "pushVapidKey",
"affinity": "TEXT"
},
{
"fieldPath": "pushSubscription",
"columnName": "pushSubscription",
"affinity": "TEXT"
},
{
"fieldPath": "pushSubscriptionExpires",
"columnName": "pushSubscriptionExpires",
"affinity": "INTEGER"
},
{
"fieldPath": "pushSubscriptionCreated",
"columnName": "pushSubscriptionCreated",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_ownerId_type",
"unique": false,
"columnNames": [
"ownerId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_ownerId_type` ON `${TABLE_NAME}` (`ownerId`, `type`)"
},
{
"name": "index_collection_pushTopic_type",
"unique": false,
"columnNames": [
"pushTopic",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_pushTopic_type` ON `${TABLE_NAME}` (`pushTopic`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
},
{
"table": "principal",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "principal",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `url` TEXT NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_principal_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_principal_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_document",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mountId` INTEGER NOT NULL, `parentId` INTEGER, `name` TEXT NOT NULL, `isDirectory` INTEGER NOT NULL, `displayName` TEXT, `mimeType` TEXT, `eTag` TEXT, `lastModified` INTEGER, `size` INTEGER, `mayBind` INTEGER, `mayUnbind` INTEGER, `mayWriteContent` INTEGER, `quotaAvailable` INTEGER, `quotaUsed` INTEGER, FOREIGN KEY(`mountId`) REFERENCES `webdav_mount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `webdav_document`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mountId",
"columnName": "mountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "INTEGER"
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isDirectory",
"columnName": "isDirectory",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT"
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT"
},
{
"fieldPath": "eTag",
"columnName": "eTag",
"affinity": "TEXT"
},
{
"fieldPath": "lastModified",
"columnName": "lastModified",
"affinity": "INTEGER"
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER"
},
{
"fieldPath": "mayBind",
"columnName": "mayBind",
"affinity": "INTEGER"
},
{
"fieldPath": "mayUnbind",
"columnName": "mayUnbind",
"affinity": "INTEGER"
},
{
"fieldPath": "mayWriteContent",
"columnName": "mayWriteContent",
"affinity": "INTEGER"
},
{
"fieldPath": "quotaAvailable",
"columnName": "quotaAvailable",
"affinity": "INTEGER"
},
{
"fieldPath": "quotaUsed",
"columnName": "quotaUsed",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_webdav_document_mountId_parentId_name",
"unique": true,
"columnNames": [
"mountId",
"parentId",
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_webdav_document_mountId_parentId_name` ON `${TABLE_NAME}` (`mountId`, `parentId`, `name`)"
},
{
"name": "index_webdav_document_parentId",
"unique": false,
"columnNames": [
"parentId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_webdav_document_parentId` ON `${TABLE_NAME}` (`parentId`)"
}
],
"foreignKeys": [
{
"table": "webdav_mount",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"mountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "webdav_document",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"parentId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cd15d368408570cc2e57252816869de2')"
]
}
}

View File

@@ -0,0 +1,648 @@
{
"formatVersion": 1,
"database": {
"version": 18,
"identityHash": "6a0f7e1553e1f621ae7913ea14370fd0",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
]
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `ownerId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `color` INTEGER, `timezoneId` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, `pushTopic` TEXT, `supportsWebPush` INTEGER NOT NULL DEFAULT 0, `pushVapidKey` TEXT, `pushSubscription` TEXT, `pushSubscriptionExpires` INTEGER, `pushSubscriptionCreated` INTEGER, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`ownerId`) REFERENCES `principal`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER"
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "INTEGER"
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT"
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT"
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER"
},
{
"fieldPath": "timezoneId",
"columnName": "timezoneId",
"affinity": "TEXT"
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER"
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER"
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER"
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT"
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pushTopic",
"columnName": "pushTopic",
"affinity": "TEXT"
},
{
"fieldPath": "supportsWebPush",
"columnName": "supportsWebPush",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "pushVapidKey",
"columnName": "pushVapidKey",
"affinity": "TEXT"
},
{
"fieldPath": "pushSubscription",
"columnName": "pushSubscription",
"affinity": "TEXT"
},
{
"fieldPath": "pushSubscriptionExpires",
"columnName": "pushSubscriptionExpires",
"affinity": "INTEGER"
},
{
"fieldPath": "pushSubscriptionCreated",
"columnName": "pushSubscriptionCreated",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_ownerId_type",
"unique": false,
"columnNames": [
"ownerId",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_ownerId_type` ON `${TABLE_NAME}` (`ownerId`, `type`)"
},
{
"name": "index_collection_pushTopic_type",
"unique": false,
"columnNames": [
"pushTopic",
"type"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_pushTopic_type` ON `${TABLE_NAME}` (`pushTopic`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
},
{
"table": "principal",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "principal",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `url` TEXT NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_principal_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_principal_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `dataType` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dataType",
"columnName": "dataType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_syncstats_collectionId_dataType",
"unique": true,
"columnNames": [
"collectionId",
"dataType"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_dataType` ON `${TABLE_NAME}` (`collectionId`, `dataType`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_document",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `mountId` INTEGER NOT NULL, `parentId` INTEGER, `name` TEXT NOT NULL, `isDirectory` INTEGER NOT NULL, `displayName` TEXT, `mimeType` TEXT, `eTag` TEXT, `lastModified` INTEGER, `size` INTEGER, `mayBind` INTEGER, `mayUnbind` INTEGER, `mayWriteContent` INTEGER, `quotaAvailable` INTEGER, `quotaUsed` INTEGER, FOREIGN KEY(`mountId`) REFERENCES `webdav_mount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `webdav_document`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mountId",
"columnName": "mountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "INTEGER"
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isDirectory",
"columnName": "isDirectory",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT"
},
{
"fieldPath": "mimeType",
"columnName": "mimeType",
"affinity": "TEXT"
},
{
"fieldPath": "eTag",
"columnName": "eTag",
"affinity": "TEXT"
},
{
"fieldPath": "lastModified",
"columnName": "lastModified",
"affinity": "INTEGER"
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER"
},
{
"fieldPath": "mayBind",
"columnName": "mayBind",
"affinity": "INTEGER"
},
{
"fieldPath": "mayUnbind",
"columnName": "mayUnbind",
"affinity": "INTEGER"
},
{
"fieldPath": "mayWriteContent",
"columnName": "mayWriteContent",
"affinity": "INTEGER"
},
{
"fieldPath": "quotaAvailable",
"columnName": "quotaAvailable",
"affinity": "INTEGER"
},
{
"fieldPath": "quotaUsed",
"columnName": "quotaUsed",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_webdav_document_mountId_parentId_name",
"unique": true,
"columnNames": [
"mountId",
"parentId",
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_webdav_document_mountId_parentId_name` ON `${TABLE_NAME}` (`mountId`, `parentId`, `name`)"
},
{
"name": "index_webdav_document_parentId",
"unique": false,
"columnNames": [
"parentId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_webdav_document_parentId` ON `${TABLE_NAME}` (`parentId`)"
}
],
"foreignKeys": [
{
"table": "webdav_mount",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"mountId"
],
"referencedColumns": [
"id"
]
},
{
"table": "webdav_document",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"parentId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "webdav_mount",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6a0f7e1553e1f621ae7913ea14370fd0')"
]
}
}

View File

@@ -0,0 +1,298 @@
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "b8699ef3cc4c62e8851df4360fb69e00",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `owner` TEXT, `color` INTEGER, `timezone` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "owner",
"columnName": "owner",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b8699ef3cc4c62e8851df4360fb69e00')"
]
}
}

View File

@@ -0,0 +1,366 @@
{
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "7e4bfdf7f9fa3529c333cf9485f8cf50",
"entities": [
{
"tableName": "service",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT NOT NULL, `type` TEXT NOT NULL, `principal` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountName",
"columnName": "accountName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "principal",
"columnName": "principal",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_service_accountName_type",
"unique": true,
"columnNames": [
"accountName",
"type"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_accountName_type` ON `${TABLE_NAME}` (`accountName`, `type`)"
}
],
"foreignKeys": []
},
{
"tableName": "homeset",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `personal` INTEGER NOT NULL, `url` TEXT NOT NULL, `privBind` INTEGER NOT NULL, `displayName` TEXT, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "personal",
"columnName": "personal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privBind",
"columnName": "privBind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_homeset_serviceId_url",
"unique": true,
"columnNames": [
"serviceId",
"url"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_homeset_serviceId_url` ON `${TABLE_NAME}` (`serviceId`, `url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "collection",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `serviceId` INTEGER NOT NULL, `homeSetId` INTEGER, `type` TEXT NOT NULL, `url` TEXT NOT NULL, `privWriteContent` INTEGER NOT NULL, `privUnbind` INTEGER NOT NULL, `forceReadOnly` INTEGER NOT NULL, `displayName` TEXT, `description` TEXT, `owner` TEXT, `color` INTEGER, `timezone` TEXT, `supportsVEVENT` INTEGER, `supportsVTODO` INTEGER, `supportsVJOURNAL` INTEGER, `source` TEXT, `sync` INTEGER NOT NULL, FOREIGN KEY(`serviceId`) REFERENCES `service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`homeSetId`) REFERENCES `homeset`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "serviceId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "homeSetId",
"columnName": "homeSetId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "privWriteContent",
"columnName": "privWriteContent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "privUnbind",
"columnName": "privUnbind",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forceReadOnly",
"columnName": "forceReadOnly",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "owner",
"columnName": "owner",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timezone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "supportsVEVENT",
"columnName": "supportsVEVENT",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVTODO",
"columnName": "supportsVTODO",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "supportsVJOURNAL",
"columnName": "supportsVJOURNAL",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sync",
"columnName": "sync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_collection_serviceId_type",
"unique": false,
"columnNames": [
"serviceId",
"type"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_serviceId_type` ON `${TABLE_NAME}` (`serviceId`, `type`)"
},
{
"name": "index_collection_homeSetId_type",
"unique": false,
"columnNames": [
"homeSetId",
"type"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_homeSetId_type` ON `${TABLE_NAME}` (`homeSetId`, `type`)"
},
{
"name": "index_collection_url",
"unique": false,
"columnNames": [
"url"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_collection_url` ON `${TABLE_NAME}` (`url`)"
}
],
"foreignKeys": [
{
"table": "service",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serviceId"
],
"referencedColumns": [
"id"
]
},
{
"table": "homeset",
"onDelete": "SET NULL",
"onUpdate": "NO ACTION",
"columns": [
"homeSetId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "syncstats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collectionId` INTEGER NOT NULL, `authority` TEXT NOT NULL, `lastSync` INTEGER NOT NULL, FOREIGN KEY(`collectionId`) REFERENCES `collection`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectionId",
"columnName": "collectionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "authority",
"columnName": "authority",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSync",
"columnName": "lastSync",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_syncstats_collectionId_authority",
"unique": true,
"columnNames": [
"collectionId",
"authority"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_syncstats_collectionId_authority` ON `${TABLE_NAME}` (`collectionId`, `authority`)"
}
],
"foreignKeys": [
{
"table": "collection",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"collectionId"
],
"referencedColumns": [
"id"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7e4bfdf7f9fa3529c333cf9485f8cf50')"
]
}
}

View File

@@ -1,28 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="at.bitfire.davdroid"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="internalOnly">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="internalOnly">
<!-- account management permissions not required for own accounts since API level 22 -->
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" android:maxSdkVersion="22"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" android:maxSdkVersion="22"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22"/>
<application>
<!--
Since Mockk 1.14.7 it's required to use minSdk 26. We use 24, so override for tests.
-->
<uses-sdk tools:overrideLibrary="io.mockk.android,io.mockk.proxy.android" />
<service
android:name=".syncadapter.NullAuthenticatorService"
android:exported="false">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/>
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/test_account_authenticator"/>
</service>
</application>
</manifest>
</manifest>

View File

@@ -1,134 +0,0 @@
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid.model
import androidx.test.filters.SmallTest
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.property.ResourceType
import at.bitfire.davdroid.HttpClient
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
class CollectionTest {
private lateinit var httpClient: HttpClient
private val server = MockWebServer()
@Before
fun setUp() {
httpClient = HttpClient.Builder().build()
}
@After
fun shutDown() {
httpClient.close()
}
@Test
@SmallTest
fun testFromDavResponseAddressBook() {
// r/w address book
server.enqueue(MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav'>" +
"<response>" +
" <href>/</href>" +
" <propstat><prop>" +
" <resourcetype><collection/><CARD:addressbook/></resourcetype>" +
" <displayname>My Contacts</displayname>" +
" <CARD:addressbook-description>My Contacts Description</CARD:addressbook-description>" +
" </prop></propstat>" +
"</response>" +
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient.okHttpClient, server.url("/"))
.propfind(0, ResourceType.NAME) { response, _ ->
info = Collection.fromDavResponse(response) ?: throw IllegalArgumentException()
}
assertEquals(Collection.TYPE_ADDRESSBOOK, info.type)
assertTrue(info.privWriteContent)
assertTrue(info.privUnbind)
assertNull(info.supportsVEVENT)
assertNull(info.supportsVTODO)
assertNull(info.supportsVJOURNAL)
assertEquals("My Contacts", info.displayName)
assertEquals("My Contacts Description", info.description)
}
@Test
@SmallTest
fun testFromDavResponseCalendar() {
// read-only calendar, no display name
server.enqueue(MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CAL='urn:ietf:params:xml:ns:caldav' xmlns:ICAL='http://apple.com/ns/ical/'>" +
"<response>" +
" <href>/</href>" +
" <propstat><prop>" +
" <resourcetype><collection/><CAL:calendar/></resourcetype>" +
" <current-user-privilege-set><privilege><read/></privilege></current-user-privilege-set>" +
" <CAL:calendar-description>My Calendar</CAL:calendar-description>" +
" <CAL:calendar-timezone>tzdata</CAL:calendar-timezone>" +
" <ICAL:calendar-color>#ff0000</ICAL:calendar-color>" +
" </prop></propstat>" +
"</response>" +
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient.okHttpClient, server.url("/"))
.propfind(0, ResourceType.NAME) { response, _ ->
info = Collection.fromDavResponse(response) ?: throw IllegalArgumentException()
}
assertEquals(Collection.TYPE_CALENDAR, info.type)
assertFalse(info.privWriteContent)
assertFalse(info.privUnbind)
assertNull(info.displayName)
assertEquals("My Calendar", info.description)
assertEquals(0xFFFF0000.toInt(), info.color)
assertEquals("tzdata", info.timezone)
assertTrue(info.supportsVEVENT!!)
assertTrue(info.supportsVTODO!!)
assertTrue(info.supportsVJOURNAL!!)
}
@Test
@SmallTest
fun testFromDavResponseWebcal() {
// Webcal subscription
server.enqueue(MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CS='http://calendarserver.org/ns/'>" +
"<response>" +
" <href>/webcal1</href>" +
" <propstat><prop>" +
" <displayname>Sample Subscription</displayname>" +
" <resourcetype><collection/><CS:subscribed/></resourcetype>" +
" <CS:source><href>webcals://example.com/1.ics</href></CS:source>" +
" </prop></propstat>" +
"</response>" +
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient.okHttpClient, server.url("/"))
.propfind(0, ResourceType.NAME) { response, _ ->
info = Collection.fromDavResponse(response) ?: throw IllegalArgumentException()
}
assertEquals(Collection.TYPE_WEBCAL, info.type)
assertEquals("Sample Subscription", info.displayName)
assertEquals("https://example.com/1.ics".toHttpUrl(), info.source)
}
}

View File

@@ -1,64 +0,0 @@
package at.bitfire.davdroid.model
import androidx.room.Room
import androidx.test.platform.app.InstrumentationRegistry
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
class DaoToolsTest {
private lateinit var db: AppDatabase
@Before
fun createDb() {
val context = InstrumentationRegistry.getInstrumentation().context
db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
}
@After
fun closeDb() {
db.close()
}
@Test
fun testSyncAll() {
val serviceDao = db.serviceDao()
val service = Service(id=0, accountName="test", type=Service.TYPE_CALDAV, principal = null)
service.id = serviceDao.insertOrReplace(service)
val homeSetDao = db.homeSetDao()
val entry1 = HomeSet(id=1, serviceId=service.id, url= "https://example.com/1".toHttpUrl())
val entry3 = HomeSet(id=3, serviceId=service.id, url= "https://example.com/3".toHttpUrl())
val oldItems = listOf(
entry1,
HomeSet(id=2, serviceId=service.id, url= "https://example.com/2".toHttpUrl()),
entry3
)
homeSetDao.insert(oldItems)
val newItems = mutableMapOf<HttpUrl, HomeSet>()
newItems[entry1.url] = entry1
// no id, because identity is given by the url
val updated = HomeSet(id=0, serviceId=service.id,
url= "https://example.com/2".toHttpUrl(), displayName="Updated Entry")
newItems[updated.url] = updated
val created = HomeSet(id=4, serviceId=service.id, url= "https://example.com/4".toHttpUrl())
newItems[created.url] = created
DaoTools(homeSetDao).syncAll(oldItems, newItems, { it.url })
val afterSync = homeSetDao.getByService(service.id)
assertEquals(afterSync.size, 3)
assertFalse(afterSync.contains(entry3))
assertTrue(afterSync.contains(entry1))
assertTrue(afterSync.contains(updated))
assertTrue(afterSync.contains(created))
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid.settings
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class SettingsManagerTest {
lateinit var settingsManager: SettingsManager
@Before
fun initialize() {
settingsManager = SettingsManager.getInstance(InstrumentationRegistry.getInstrumentation().targetContext)
}
@Test
fun testContainsKey() {
assertFalse(settingsManager.containsKey("notExisting"))
// provided by DefaultsProvider
assertTrue(settingsManager.containsKey(Settings.OVERRIDE_PROXY))
}
}

View File

@@ -1,43 +0,0 @@
package at.bitfire.davdroid.syncadapter
import android.net.Uri
import at.bitfire.davdroid.resource.LocalResource
class LocalTestResource: LocalResource<Any> {
override val id: Long? = null
override var fileName: String? = null
override var eTag: String? = null
override var scheduleTag: String? = null
override var flags: Int = 0
var deleted = false
var dirty = false
override fun prepareForUpload() = "generated-file.txt"
override fun clearDirty(fileName: String?, eTag: String?, scheduleTag: String?) {
dirty = false
if (fileName != null)
this.fileName = fileName
this.eTag = eTag
this.scheduleTag = scheduleTag
}
override fun updateFlags(flags: Int) {
this.flags = flags
}
override fun add(): Uri {
TODO("Not yet implemented")
}
override fun update(data: Any): Uri {
TODO("Not yet implemented")
}
override fun delete(): Int {
TODO("Not yet implemented")
}
}

View File

@@ -1,31 +0,0 @@
package at.bitfire.davdroid.syncadapter
import android.os.Bundle
import androidx.test.filters.SmallTest
import at.bitfire.davdroid.syncadapter.SyncAdapterService.SyncAdapter
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class SyncAdapterServiceTest {
@Test
@SmallTest
fun testPriorityCollections() {
val extras = Bundle()
assertTrue(SyncAdapter.priorityCollections(extras).isEmpty())
extras.putString(SyncAdapterService.SYNC_EXTRAS_PRIORITY_COLLECTIONS, "")
assertTrue(SyncAdapter.priorityCollections(extras).isEmpty())
extras.putString(SyncAdapterService.SYNC_EXTRAS_PRIORITY_COLLECTIONS, "123")
assertArrayEquals(longArrayOf(123), SyncAdapter.priorityCollections(extras).toLongArray())
extras.putString(SyncAdapterService.SYNC_EXTRAS_PRIORITY_COLLECTIONS, ",x,")
assertTrue(SyncAdapter.priorityCollections(extras).isEmpty())
extras.putString(SyncAdapterService.SYNC_EXTRAS_PRIORITY_COLLECTIONS, "1,2,3")
assertArrayEquals(longArrayOf(1,2,3), SyncAdapter.priorityCollections(extras).toLongArray())
}
}

View File

@@ -1,102 +0,0 @@
package at.bitfire.davdroid.syncadapter
import android.accounts.Account
import android.content.Context
import android.content.SyncResult
import android.os.Bundle
import at.bitfire.dav4jvm.DavCollection
import at.bitfire.dav4jvm.DavResponseCallback
import at.bitfire.dav4jvm.Response
import at.bitfire.dav4jvm.UrlUtils
import at.bitfire.dav4jvm.property.GetCTag
import at.bitfire.davdroid.DavUtils
import at.bitfire.davdroid.model.SyncState
import at.bitfire.davdroid.resource.LocalResource
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.ical4android.Ical4Android
import okhttp3.HttpUrl
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.mockwebserver.MockWebServer
import org.junit.Assert.assertEquals
class TestSyncManager(
context: Context,
account: Account,
extras: Bundle,
authority: String,
syncResult: SyncResult,
localCollection: LocalTestCollection,
val mockWebServer: MockWebServer
): SyncManager<LocalTestResource, LocalTestCollection, DavCollection>(context, account, AccountSettings(context, account), extras, authority, syncResult, localCollection) {
override fun prepare(): Boolean {
collectionURL = mockWebServer.url("/")
davCollection = DavCollection(httpClient.okHttpClient, collectionURL)
return true
}
var didQueryCapabilities = false
override fun queryCapabilities(): SyncState? {
if (didQueryCapabilities)
throw IllegalStateException("queryCapabilities() must not be called twice")
didQueryCapabilities = true
var cTag: SyncState? = null
davCollection.propfind(0, GetCTag.NAME) { response, rel ->
if (rel == Response.HrefRelation.SELF)
response[GetCTag::class.java]?.cTag?.let {
cTag = SyncState(SyncState.Type.CTAG, it)
}
}
return cTag
}
var didGenerateUpload = false
override fun generateUpload(resource: LocalTestResource): RequestBody {
didGenerateUpload = true
return resource.toString().toRequestBody()
}
override fun syncAlgorithm() = SyncAlgorithm.PROPFIND_REPORT
var listAllRemoteResult = emptyList<Pair<Response, Response.HrefRelation>>()
var didListAllRemote = false
override fun listAllRemote(callback: DavResponseCallback) {
if (didListAllRemote)
throw IllegalStateException("listAllRemote() must not be called twice")
didListAllRemote = true
for (result in listAllRemoteResult)
callback(result.first, result.second)
}
var assertDownloadRemote = emptyMap<HttpUrl, String>()
var didDownloadRemote = false
override fun downloadRemote(bunch: List<HttpUrl>) {
didDownloadRemote = true
assertEquals(assertDownloadRemote.keys.toList(), bunch)
for ((url, eTag) in assertDownloadRemote) {
val fileName = DavUtils.lastSegmentOfUrl(url)
var localEntry = localCollection.entries.filter { it.fileName == fileName }.firstOrNull()
if (localEntry == null) {
val newEntry = LocalTestResource().also {
it.fileName = fileName
}
localCollection.entries += newEntry
localEntry = newEntry
}
localEntry.eTag = eTag
localEntry.flags = LocalResource.FLAG_REMOTELY_PRESENT
}
}
override fun postProcess() {
}
override fun notifyInvalidResourceTitle(): String {
TODO("Not yet implemented")
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid
import android.util.Xml
import at.bitfire.dav4jvm.XmlUtils
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class ExternalLibrariesTest {
@Test
fun test_Dav4jvm_XmlUtils_NewPullParser_RelaxedParsing() {
val parser = XmlUtils.newPullParser()
assertTrue(parser.getFeature(Xml.FEATURE_RELAXED))
}
@Test
fun testOkhttpHttpUrl_PublicSuffixList() {
// HttpUrl.topPrivateDomain() requires okhttp's internal PublicSuffixList.
// In Android, loading the PublicSuffixList is done over AndroidX startup.
// This test verifies that everything is working.
assertEquals("example.com", "http://example.com".toHttpUrl().topPrivateDomain())
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid
import android.app.Application
import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.test.runner.AndroidJUnitRunner
import at.bitfire.davdroid.di.TestCoroutineDispatchersModule
import at.bitfire.davdroid.test.BuildConfig
import at.bitfire.synctools.log.LogcatHandler
import dagger.hilt.android.testing.HiltTestApplication
import java.util.logging.Level
import java.util.logging.Logger
@Suppress("unused")
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader, name: String, context: Context): Application =
super.newApplication(cl, HiltTestApplication::class.java.name, context)
override fun onCreate(arguments: Bundle?) {
super.onCreate(arguments)
// set root logger to adb Logcat
val rootLogger = Logger.getLogger("")
rootLogger.level = Level.ALL
rootLogger.handlers.forEach { rootLogger.removeHandler(it) }
rootLogger.addHandler(LogcatHandler(BuildConfig.APPLICATION_ID))
// MockK requirements
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
throw AssertionError("MockK requires Android P [https://mockk.io/ANDROID.html]")
// set main dispatcher for tests (especially runTest)
TestCoroutineDispatchersModule.initMainDispatcher()
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid
import android.content.Context
import android.util.Log
import androidx.work.Configuration
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.WorkerFactory
import androidx.work.testing.WorkManagerTestInitHelper
import org.junit.Assert.assertTrue
import kotlin.math.abs
object TestUtils {
fun assertWithin(expected: Long, actual: Long, tolerance: Long) {
val absDifference = abs(expected - actual)
assertTrue(
"$actual not within ($expected ± $tolerance)",
absDifference <= tolerance
)
}
/**
* Initializes WorkManager for instrumentation tests.
*/
fun setUpWorkManager(context: Context, workerFactory: WorkerFactory? = null) {
val config = Configuration.Builder().setMinimumLoggingLevel(Log.DEBUG)
if (workerFactory != null)
config.setWorkerFactory(workerFactory)
WorkManagerTestInitHelper.initializeTestWorkManager(context, config.build())
}
fun workInStates(context: Context, workerName: String, states: List<WorkInfo.State>): Boolean =
WorkManager.getInstance(context).getWorkInfos(WorkQuery.Builder
.fromUniqueWorkNames(listOf(workerName))
.addStates(states)
.build()
).get().isNotEmpty()
fun workScheduledOrRunning(context: Context, workerName: String): Boolean =
workInStates(context, workerName, listOf(
WorkInfo.State.ENQUEUED,
WorkInfo.State.RUNNING
))
fun workScheduledOrRunningOrSuccessful(context: Context, workerName: String): Boolean =
workInStates(context, workerName, listOf(
WorkInfo.State.ENQUEUED,
WorkInfo.State.RUNNING,
WorkInfo.State.SUCCEEDED
))
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import android.content.Context
import androidx.room.Room
import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.platform.app.InstrumentationRegistry
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.logging.Logger
import javax.inject.Inject
@HiltAndroidTest
class AppDatabaseTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var autoMigrations: Set<@JvmSuppressWildcards AutoMigrationSpec>
@Inject
lateinit var logger: Logger
@Inject
lateinit var manualMigrations: Set<@JvmSuppressWildcards Migration>
@Before
fun setup() {
hiltRule.inject()
}
/**
* Creates a database with schema version 8 (the first exported one) and then migrates it to the latest version.
*/
@Test
fun testAllMigrations() {
// Create DB with v8
MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java,
listOf(), // no auto migrations until v8
FrameworkSQLiteOpenHelperFactory()
).createDatabase(TEST_DB, 8).close()
// open and migrate (to current version) database
Room.databaseBuilder(context, AppDatabase::class.java, TEST_DB)
// manual migrations
.addMigrations(*manualMigrations.toTypedArray())
// auto-migrations that need to be specified explicitly
.apply {
for (spec in autoMigrations)
addAutoMigrationSpec(spec)
}
.build()
.openHelper.writableDatabase // this will run all migrations
.close()
}
companion object {
const val TEST_DB = "test"
}
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import android.security.NetworkSecurityPolicy
import androidx.test.filters.SmallTest
import at.bitfire.dav4jvm.okhttp.DavResource
import at.bitfire.dav4jvm.property.webdav.WebDAV
import at.bitfire.davdroid.network.HttpClientBuilder
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class CollectionTest {
@Inject
lateinit var httpClientBuilder: HttpClientBuilder
@get:Rule
val hiltRule = HiltAndroidRule(this)
private lateinit var httpClient: OkHttpClient
private val server = MockWebServer()
@Before
fun setup() {
hiltRule.inject()
httpClient = httpClientBuilder.build()
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
}
@Test
@SmallTest
fun testFromDavResponseAddressBook() {
// r/w address book
server.enqueue(MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav'>" +
"<response>" +
" <href>/</href>" +
" <propstat><prop>" +
" <resourcetype><collection/><CARD:addressbook/></resourcetype>" +
" <displayname>My Contacts</displayname>" +
" <CARD:addressbook-description>My Contacts Description</CARD:addressbook-description>" +
" </prop></propstat>" +
"</response>" +
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient, server.url("/"))
.propfind(0, WebDAV.ResourceType) { response, _ ->
info = Collection.fromDavResponse(response) ?: throw IllegalArgumentException()
}
assertEquals(Collection.TYPE_ADDRESSBOOK, info.type)
assertTrue(info.privWriteContent)
assertTrue(info.privUnbind)
assertNull(info.supportsVEVENT)
assertNull(info.supportsVTODO)
assertNull(info.supportsVJOURNAL)
assertEquals("My Contacts", info.displayName)
assertEquals("My Contacts Description", info.description)
}
@Test
@SmallTest
fun testFromDavResponseCalendar_FullTimezone() {
// read-only calendar, no display name
server.enqueue(MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CAL='urn:ietf:params:xml:ns:caldav' xmlns:ICAL='http://apple.com/ns/ical/'>" +
"<response>" +
" <href>/</href>" +
" <propstat><prop>" +
" <resourcetype><collection/><CAL:calendar/></resourcetype>" +
" <current-user-privilege-set><privilege><read/></privilege></current-user-privilege-set>" +
" <CAL:calendar-description>My Calendar</CAL:calendar-description>" +
" <CAL:calendar-timezone>BEGIN:VCALENDAR\n" +
"PRODID:-//Example Corp.//CalDAV Client//EN\n" +
"VERSION:2.0\n" +
"BEGIN:VTIMEZONE\n" +
"TZID:US-Eastern\n" +
"LAST-MODIFIED:19870101T000000Z\n" +
"BEGIN:STANDARD\n" +
"DTSTART:19671029T020000\n" +
"RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" +
"TZOFFSETFROM:-0400\n" +
"TZOFFSETTO:-0500\n" +
"TZNAME:Eastern Standard Time (US & Canada)\n" +
"END:STANDARD\n" +
"BEGIN:DAYLIGHT\n" +
"DTSTART:19870405T020000\n" +
"RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\n" +
"TZOFFSETFROM:-0500\n" +
"TZOFFSETTO:-0400\n" +
"TZNAME:Eastern Daylight Time (US & Canada)\n" +
"END:DAYLIGHT\n" +
"END:VTIMEZONE\n" +
"END:VCALENDAR\n" +
"</CAL:calendar-timezone>" +
" <ICAL:calendar-color>#ff0000</ICAL:calendar-color>" +
" </prop></propstat>" +
"</response>" +
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient, server.url("/"))
.propfind(0, WebDAV.ResourceType) { response, _ ->
info = Collection.fromDavResponse(response)!!
}
assertEquals(Collection.TYPE_CALENDAR, info.type)
assertFalse(info.privWriteContent)
assertFalse(info.privUnbind)
assertNull(info.displayName)
assertEquals("My Calendar", info.description)
assertEquals(0xFFFF0000.toInt(), info.color)
assertEquals("US-Eastern", info.timezoneId)
assertTrue(info.supportsVEVENT!!)
assertTrue(info.supportsVTODO!!)
assertTrue(info.supportsVJOURNAL!!)
}
@Test
@SmallTest
fun testFromDavResponseCalendar_OnlyTzId() {
// read-only calendar, no display name
server.enqueue(MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CAL='urn:ietf:params:xml:ns:caldav' xmlns:ICAL='http://apple.com/ns/ical/'>" +
"<response>" +
" <href>/</href>" +
" <propstat><prop>" +
" <resourcetype><collection/><CAL:calendar/></resourcetype>" +
" <current-user-privilege-set><privilege><read/></privilege></current-user-privilege-set>" +
" <CAL:calendar-description>My Calendar</CAL:calendar-description>" +
" <CAL:calendar-timezone-id>US-Eastern</CAL:calendar-timezone-id>" +
" <ICAL:calendar-color>#ff0000</ICAL:calendar-color>" +
" </prop></propstat>" +
"</response>" +
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient, server.url("/"))
.propfind(0, WebDAV.ResourceType) { response, _ ->
info = Collection.fromDavResponse(response)!!
}
assertEquals(Collection.TYPE_CALENDAR, info.type)
assertFalse(info.privWriteContent)
assertFalse(info.privUnbind)
assertNull(info.displayName)
assertEquals("My Calendar", info.description)
assertEquals(0xFFFF0000.toInt(), info.color)
assertEquals("US-Eastern", info.timezoneId)
assertTrue(info.supportsVEVENT!!)
assertTrue(info.supportsVTODO!!)
assertTrue(info.supportsVJOURNAL!!)
}
@Test
@SmallTest
fun testFromDavResponseWebcal() {
// Webcal subscription
server.enqueue(MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CS='http://calendarserver.org/ns/'>" +
"<response>" +
" <href>/webcal1</href>" +
" <propstat><prop>" +
" <displayname>Sample Subscription</displayname>" +
" <resourcetype><collection/><CS:subscribed/></resourcetype>" +
" <CS:source><href>webcals://example.com/1.ics</href></CS:source>" +
" </prop></propstat>" +
"</response>" +
"</multistatus>"))
lateinit var info: Collection
DavResource(httpClient, server.url("/"))
.propfind(0, WebDAV.ResourceType) { response, _ ->
info = Collection.fromDavResponse(response) ?: throw IllegalArgumentException()
}
assertEquals(Collection.TYPE_WEBCAL, info.type)
assertEquals("Sample Subscription", info.displayName)
assertEquals("https://example.com/1.ics".toHttpUrl(), info.source)
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class HomeSetDaoTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Inject
lateinit var db: AppDatabase
lateinit var dao: HomeSetDao
var serviceId: Long = 0
@Before
fun setUp() {
hiltRule.inject()
dao = db.homeSetDao()
serviceId = db.serviceDao().insertOrReplace(
Service(id=0, accountName="test", type= Service.TYPE_CALDAV, principal = null)
)
}
@After
fun tearDown() {
db.serviceDao().deleteAll()
}
@Test
fun testInsertOrUpdate() {
// should insert new row or update (upsert) existing row - without changing its key!
val entry1 = HomeSet(id=0, serviceId=serviceId, personal=true, url="https://example.com/1".toHttpUrl())
val insertId1 = dao.insertOrUpdateByUrlBlocking(entry1)
assertEquals(1L, insertId1)
assertEquals(entry1.copy(id = 1L), dao.getById(1))
val updatedEntry1 = HomeSet(id=0, serviceId=serviceId, personal=true, url="https://example.com/1".toHttpUrl(), displayName="Updated Entry")
val updateId1 = dao.insertOrUpdateByUrlBlocking(updatedEntry1)
assertEquals(1L, updateId1)
assertEquals(updatedEntry1.copy(id = 1L), dao.getById(1))
val entry2 = HomeSet(id=0, serviceId=serviceId, personal=true, url= "https://example.com/2".toHttpUrl())
val insertId2 = dao.insertOrUpdateByUrlBlocking(entry2)
assertEquals(2L, insertId2)
assertEquals(entry2.copy(id = 2L), dao.getById(2))
}
@Test
fun testInsertOrUpdate_TransactionSafe() {
runBlocking(Dispatchers.IO) {
for (i in 0..9999)
launch {
dao.insertOrUpdateByUrlBlocking(
HomeSet(
id = 0,
serviceId = serviceId,
url = "https://example.com/".toHttpUrl(),
personal = true
)
)
}
}
assertEquals(1, dao.getByService(serviceId).size)
}
@Test
fun testDelete() {
// should delete row with given primary key (id)
val entry1 = HomeSet(id=1, serviceId=serviceId, personal=true, url= "https://example.com/1".toHttpUrl())
val insertId1 = dao.insertOrUpdateByUrlBlocking(entry1)
assertEquals(1L, insertId1)
assertEquals(entry1, dao.getById(1L))
dao.delete(entry1)
assertEquals(null, dao.getById(1L))
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import android.content.Context
import androidx.room.Room
import androidx.room.migration.AutoMigrationSpec
import dagger.Module
import dagger.Provides
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import javax.inject.Singleton
@Module
@TestInstallIn(
components = [ SingletonComponent::class ],
replaces = [
AppDatabase.AppDatabaseModule::class
]
)
class MemoryDbModule {
@Provides
@Singleton
fun inMemoryDatabase(
autoMigrations: Set<@JvmSuppressWildcards AutoMigrationSpec>,
@ApplicationContext context: Context
): AppDatabase =
Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
// auto-migration specs that need to be specified explicitly
.apply {
for (spec in autoMigrations) {
addAutoMigrationSpec(spec)
}
}
.build()
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import android.database.sqlite.SQLiteConstraintException
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.junit4.MockKRule
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class PrincipalDaoTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockKRule = MockKRule(this)
@Inject
lateinit var db: AppDatabase
private lateinit var principalDao: PrincipalDao
private lateinit var service: Service
private val url = "https://example.com/dav/principal".toHttpUrl()
@Before
fun setUp() {
hiltRule.inject()
principalDao = spyk(db.principalDao())
service = Service(id = 1, accountName = "account", type = "webdav")
db.serviceDao().insertOrReplace(service)
}
@Test
fun insertOrUpdate_insertsIfNotExisting() = runTest {
val principal = Principal(serviceId = service.id, url = url, displayName = "principal")
val id = principalDao.insertOrUpdate(service.id, principal)
assertTrue(id > 0)
val stored = principalDao.get(id)
assertEquals("principal", stored.displayName)
verify(exactly = 0) { principalDao.update(any()) }
}
@Test
fun insertOrUpdate_doesNotUpdateIfDisplayNameIsEqual() = runTest {
val principalOld = Principal(serviceId = service.id, url = url, displayName = "principalOld")
val idOld = principalDao.insertOrUpdate(service.id, principalOld)
val principalNew = Principal(serviceId = service.id, url = url, displayName = "principalOld")
val idNew = principalDao.insertOrUpdate(service.id, principalNew)
assertEquals(idOld, idNew)
val stored = principalDao.get(idOld)
assertEquals("principalOld", stored.displayName)
verify(exactly = 0) { principalDao.update(any()) }
}
@Test
fun insertOrUpdate_updatesIfDisplayNameIsDifferent() = runTest {
val principalOld = Principal(serviceId = service.id, url = url, displayName = "principalOld")
val idOld = principalDao.insertOrUpdate(service.id, principalOld)
val principalNew = Principal(serviceId = service.id, url = url, displayName = "principalNew")
val idNew = principalDao.insertOrUpdate(service.id, principalNew)
assertEquals(idOld, idNew)
val updated = principalDao.get(idOld)
assertEquals("principalNew", updated.displayName)
verify(exactly = 1) { principalDao.update(any()) }
}
@Test(expected = SQLiteConstraintException::class)
fun insertOrUpdate_throwsForeignKeyConstraintViolationException() = runTest {
// throws on non-existing service
val url = "https://example.com/dav/principal".toHttpUrl()
val principal1 = Principal(serviceId = 999, url = url, displayName = "p1")
principalDao.insertOrUpdate(999, principal1)
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import androidx.sqlite.SQLiteException
import at.bitfire.davdroid.sync.SyncDataType
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.test.runTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class SyncStatsDaoTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var db: AppDatabase
var collectionId: Long = 0
@Before
fun setUp() {
hiltRule.inject()
val serviceId = db.serviceDao().insertOrReplace(Service(
id = 0,
accountName = "test@example.com",
type = Service.TYPE_CALDAV
))
collectionId = db.collectionDao().insert(Collection(
id = 0,
serviceId = serviceId,
type = Collection.TYPE_CALENDAR,
url = "https://example.com".toHttpUrl()
))
}
@After
fun tearDown() {
db.serviceDao().deleteAll()
}
@Test
fun testInsertOrReplace_ExistingForeignKey() = runTest {
val dao = db.syncStatsDao()
dao.insertOrReplace(
SyncStats(
id = 0,
collectionId = collectionId,
dataType = SyncDataType.CONTACTS.toString(),
lastSync = System.currentTimeMillis()
)
)
}
@Test(expected = SQLiteException::class)
fun testInsertOrReplace_MissingForeignKey() = runTest {
val dao = db.syncStatsDao()
dao.insertOrReplace(
SyncStats(
id = 0,
collectionId = 12345,
dataType = SyncDataType.CONTACTS.toString(),
lastSync = System.currentTimeMillis()
)
)
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.test.runTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
@HiltAndroidTest
class WebDavDocumentDaoTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var logger: Logger
@Before
fun setUp() {
hiltRule.inject()
}
@Test
fun testGetChildren() = runTest {
val mountDao = db.webDavMountDao()
val dao = db.webDavDocumentDao()
val mount = WebDavMount(id = 1, name = "Test", url = "https://example.com/".toHttpUrl())
db.webDavMountDao().insert(mount)
val root = WebDavDocument(
id = 1,
mountId = mount.id,
parentId = null,
name = "Root Document"
)
dao.insertOrReplace(root)
dao.insertOrReplace(WebDavDocument(id = 0, mountId = mount.id, parentId = root.id, name = "Name 1", displayName = "DisplayName 2"))
dao.insertOrReplace(WebDavDocument(id = 0, mountId = mount.id, parentId = root.id, name = "Name 2", displayName = "DisplayName 1"))
dao.insertOrReplace(WebDavDocument(id = 0, mountId = mount.id, parentId = root.id, name = "Name 3", displayName = "Directory 1", isDirectory = true))
try {
dao.getChildren(root.id, orderBy = "name DESC").let { result ->
logger.log(Level.INFO, "getChildren single sort Result", result)
assertEquals(listOf(
"Name 3",
"Name 2",
"Name 1"
), result.map { it.name })
}
dao.getChildren(root.id, orderBy = "isDirectory DESC, name ASC").let { result ->
logger.log(Level.INFO, "getChildren multiple sort Result", result)
assertEquals(listOf(
"Name 3",
"Name 1",
"Name 2"
), result.map { it.name })
}
} finally {
mountDao.deleteAsync(mount)
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db.migration
import at.bitfire.davdroid.db.Collection.Companion.TYPE_CALENDAR
import at.bitfire.davdroid.db.Service
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
@HiltAndroidTest
class AutoMigration16Test: DatabaseMigrationTest(toVersion = 16) {
@Test
fun testMigrate_WithTimeZone() = testMigration(
prepare = { db ->
val minimalVTimezone = """
BEGIN:VCALENDAR
VERSION:2.0
PRODID:DAVx5
BEGIN:VTIMEZONE
TZID:America/New_York
END:VTIMEZONE
END:VCALENDAR
""".trimIndent()
db.execSQL(
"INSERT INTO service (id, accountName, type) VALUES (?, ?, ?)",
arrayOf(1, "test", Service.Companion.TYPE_CALDAV)
)
db.execSQL(
"INSERT INTO collection (id, serviceId, type, url, privWriteContent, privUnbind, forceReadOnly, sync, timezone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
arrayOf(1, 1, TYPE_CALENDAR, "https://example.com", true, true, false, false, minimalVTimezone)
)
}
) { db ->
db.query("SELECT timezoneId FROM collection WHERE id=1").use { cursor ->
cursor.moveToFirst()
assertEquals("America/New_York", cursor.getString(0))
}
}
@Test
fun testMigrate_WithTimeZone_Unparseable() = testMigration(
prepare = { db ->
db.execSQL(
"INSERT INTO service (id, accountName, type) VALUES (?, ?, ?)",
arrayOf(1, "test", Service.Companion.TYPE_CALDAV)
)
db.execSQL(
"INSERT INTO collection (id, serviceId, type, url, privWriteContent, privUnbind, forceReadOnly, sync, timezone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
arrayOf(1, 1, TYPE_CALENDAR, "https://example.com", true, true, false, false, "Some Garbage Content")
)
}
) { db ->
db.query("SELECT timezoneId FROM collection WHERE id=1").use { cursor ->
cursor.moveToFirst()
assertNull(cursor.getString(0))
}
}
@Test
fun testMigrate_WithoutTimezone() = testMigration(
prepare = { db ->
db.execSQL(
"INSERT INTO service (id, accountName, type) VALUES (?, ?, ?)",
arrayOf(1, "test", Service.Companion.TYPE_CALDAV)
)
db.execSQL(
"INSERT INTO collection (id, serviceId, type, url, privWriteContent, privUnbind, forceReadOnly, sync) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
arrayOf(1, 1, TYPE_CALENDAR, "https://example.com", true, true, false, false)
)
}
) { db ->
db.query("SELECT timezoneId FROM collection WHERE id=1").use { cursor ->
cursor.moveToFirst()
assertNull(cursor.getString(0))
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db.migration
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Assert.assertEquals
import org.junit.Test
@HiltAndroidTest
class AutoMigration18Test : DatabaseMigrationTest(toVersion = 18) {
@Test
fun testMigration_AllAuthorities() = testMigration(
prepare = { db ->
// Insert service and collection to respect relation constraints
db.execSQL("INSERT INTO service (id, accountName, type) VALUES (?, ?, ?)", arrayOf<Any?>(1, "test", 1))
listOf(1L, 2L, 3L).forEach { id ->
db.execSQL(
"INSERT INTO collection (id, serviceId, url, type, privWriteContent, privUnbind, forceReadOnly, sync) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
arrayOf<Any?>(id, 1, "https://example.com/$id", 1, 1, 1, 0, 1)
)
}
// Insert some syncstats with authorities and lastSync times
val syncstats = listOf(
Entry(1, 1, "com.android.contacts", 1000),
Entry(2, 1, "com.android.calendar", 1000),
Entry(3, 1, "org.dmfs.tasks", 1000),
Entry(4, 1, "org.tasks.opentasks", 2000),
Entry(5, 1, "at.techbee.jtx.provider", 3000), // highest lastSync for collection 1
Entry(6, 1, "unknown.authority", 1000), // ignored
Entry(7, 2, "org.dmfs.tasks", 1000),
Entry(8, 2, "org.tasks.opentasks", 2000), // highest lastSync for collection 2
Entry(9, 3, "org.tasks.opentasks", 1000),
)
syncstats.forEach { (id, collectionId, authority, lastSync) ->
db.execSQL(
"INSERT INTO syncstats (id, collectionId, authority, lastSync) VALUES (?, ?, ?, ?)",
arrayOf<Any?>(id, collectionId, authority, lastSync)
)
}
},
validate = { db ->
db.query("SELECT id, collectionId, dataType FROM syncstats ORDER BY id").use { cursor ->
val found = mutableListOf<Entry>()
db.query("SELECT id, collectionId, dataType FROM syncstats ORDER BY id").use { cursor ->
val idIdx = cursor.getColumnIndex("id")
val colIdx = cursor.getColumnIndex("collectionId")
val typeIdx = cursor.getColumnIndex("dataType")
while (cursor.moveToNext())
found.add(
Entry(cursor.getInt(idIdx), cursor.getLong(colIdx), cursor.getString(typeIdx))
)
}
// Expect one TASKS row per collection (collections 1, 2, 3)
assertEquals(
listOf(
Entry(1, 1, "CONTACTS"),
Entry(2, 1, "EVENTS"),
Entry(5, 1, "TASKS"), // highest lastSync TASK for collection 1 is JTX Board
Entry(8, 2, "TASKS"), // highest lastSync TASK for collection 2
Entry(9, 3, "TASKS"), // only TASK for collection 3
), found
)
}
}
)
data class Entry(
val id: Int,
val collectionId: Long,
val dataType: String? = null,
val lastSync: Long? = null
)
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db.migration
import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.platform.app.InstrumentationRegistry
import at.bitfire.davdroid.db.AppDatabase
import dagger.hilt.android.testing.HiltAndroidRule
import org.junit.Before
import org.junit.Rule
import javax.inject.Inject
/**
* Helper for testing the database migration from [toVersion] - 1 to [toVersion].
*
* @param toVersion The target version to migrate to.
*/
abstract class DatabaseMigrationTest(
private val toVersion: Int
) {
@Inject
lateinit var autoMigrations: Set<@JvmSuppressWildcards AutoMigrationSpec>
@Inject
lateinit var manualMigrations: Set<@JvmSuppressWildcards Migration>
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Before
fun setup() {
hiltRule.inject()
}
/**
* Used for testing the migration process from [toVersion]-1 to [toVersion].
*
* Note: SQLite's foreign key constraint enforcement is not enabled in tests. We need
* to enable it ourselves using setting "PRAGMA foreign_keys=ON" directly after opening
* a new database connection (works per connection). In tests it's usually more practical
* not to do so, however. In production database connections room enables it for us.
*
* @param prepare Callback to prepare the database. Will be run with database schema in version [toVersion] - 1.
* @param validate Callback to validate the migration result. Will be run with database schema in version [toVersion].
*/
protected fun testMigration(
prepare: (SupportSQLiteDatabase) -> Unit,
validate: (SupportSQLiteDatabase) -> Unit
) {
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java,
autoMigrations.toList(),
FrameworkSQLiteOpenHelperFactory()
)
// Prepare the database with the initial version.
val dbName = "test"
helper.createDatabase(dbName, version = toVersion - 1).apply {
// We could enable foreign key constraint enforcement here
// by setting "PRAGMA foreign_keys=ON".
prepare(this)
close()
}
// Re-open the database with the new version and provide all the migrations.
val db = helper.runMigrationsAndValidate(
name = dbName,
version = toVersion,
validateDroppedTables = true,
migrations = manualMigrations.toTypedArray()
)
validate(db)
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.di
import at.bitfire.davdroid.sync.FakeSyncAdapter
import at.bitfire.davdroid.sync.adapter.SyncAdapter
import at.bitfire.davdroid.sync.adapter.SyncAdapterImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
@Module
@TestInstallIn(components = [SingletonComponent::class], replaces = [SyncAdapterImpl.RealSyncAdapterModule::class])
abstract class FakeSyncAdapterModule {
@Binds
abstract fun provide(impl: FakeSyncAdapter): SyncAdapter
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.di
import at.bitfire.davdroid.di.TestCoroutineDispatchersModule.standardTestDispatcher
import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
/**
* Provides test dispatchers to be injected instead of the normal ones.
*
* The [standardTestDispatcher] is set as main dispatcher in [at.bitfire.davdroid.HiltTestRunner],
* so that tests can just use [kotlinx.coroutines.test.runTest] without providing [standardTestDispatcher].
*/
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [CoroutineDispatchersModule::class]
)
object TestCoroutineDispatchersModule {
private val standardTestDispatcher = StandardTestDispatcher()
@Provides
@DefaultDispatcher
fun defaultDispatcher(): CoroutineDispatcher = standardTestDispatcher
@Provides
@IoDispatcher
fun ioDispatcher(): CoroutineDispatcher = standardTestDispatcher
@Provides
@MainDispatcher
fun mainDispatcher(): CoroutineDispatcher = standardTestDispatcher
@Provides
@SyncDispatcher
fun syncDispatcher(): CoroutineDispatcher = standardTestDispatcher
/**
* Sets the [standardTestDispatcher] as [Dispatchers.Main] so that test dispatchers
* created in the future use the same scheduler. See [StandardTestDispatcher] docs
* for more information.
*/
@OptIn(ExperimentalCoroutinesApi::class)
fun initMainDispatcher() {
Dispatchers.setMain(standardTestDispatcher)
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.di
import at.bitfire.davdroid.startup.StartupPlugin
import at.bitfire.davdroid.startup.TasksAppWatcher
import dagger.Module
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import dagger.multibindings.Multibinds
// remove TasksAppWatcherModule from Android tests
@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [TasksAppWatcher.TasksAppWatcherModule::class]
)
abstract class TestTasksAppWatcherModule {
// provides empty set of plugins
@Multibinds
abstract fun empty(): Set<StartupPlugin>
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import android.os.Build
import androidx.test.filters.SdkSuppress
import org.junit.Assert.assertEquals
import org.junit.Test
import org.xbill.DNS.ARecord
import org.xbill.DNS.Lookup
import org.xbill.DNS.Type
import java.net.Inet4Address
import java.net.InetAddress
class Android10ResolverTest {
val FQDN_DAVX5 = "www.davx5.com"
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q, maxSdkVersion = 34)
fun testResolveA() {
val www = InetAddress.getAllByName(FQDN_DAVX5).filterIsInstance<Inet4Address>().first()
val srvLookup = Lookup(FQDN_DAVX5, Type.A)
srvLookup.setResolver(Android10Resolver())
val resultGeneric = srvLookup.run()
assertEquals(1, resultGeneric.size)
val result = resultGeneric.first() as ARecord
assertEquals(www, result.address)
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import org.conscrypt.Conscrypt
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.security.Security
class ConscryptIntegrationTest {
val integration = ConscryptIntegration()
@Test
fun testInitialize_InstallsConscrypt() {
uninstallConscrypt()
assertFalse(integration.conscryptInstalled())
integration.initialize()
assertTrue(integration.conscryptInstalled())
}
private fun uninstallConscrypt() {
for (conscrypt in Security.getProviders().filter { Conscrypt.isConscrypt(it) })
Security.removeProvider(conscrypt.name)
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.xbill.DNS.DClass
import org.xbill.DNS.Name
import org.xbill.DNS.SRVRecord
import org.xbill.DNS.TXTRecord
import javax.inject.Inject
import kotlin.random.Random
@HiltAndroidTest
class DnsRecordResolverTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var dnsRecordResolver: DnsRecordResolver
@Before
fun setup() {
hiltRule.inject()
}
@Test
fun testBestSRVRecord_Empty() {
assertNull(dnsRecordResolver.bestSRVRecord(emptyArray()))
}
@Test
fun testBestSRVRecord_MultipleRecords_Priority_Different() {
val dns1010 = SRVRecord(
Name.fromString("_caldavs._tcp.example.com."),
DClass.IN, 3600, 10, 10, 8443, Name.fromString("dav1010.example.com.")
)
val dns2010 = SRVRecord(
Name.fromString("_caldavs._tcp.example.com."),
DClass.IN, 3600, 20, 20, 8443, Name.fromString("dav2010.example.com.")
)
// lowest priority first
val result = dnsRecordResolver.bestSRVRecord(arrayOf(dns1010, dns2010))
assertEquals(dns1010, result)
}
@Test
fun testBestSRVRecord_MultipleRecords_Priority_Same() {
val dns1010 = SRVRecord(
Name.fromString("_caldavs._tcp.example.com."),
DClass.IN, 3600, 10, 10, 8443, Name.fromString("dav1010.example.com.")
)
val dns1020 = SRVRecord(
Name.fromString("_caldavs._tcp.example.com."),
DClass.IN, 3600, 10, 20, 8443, Name.fromString("dav1020.example.com.")
)
val dns1030 = SRVRecord(
Name.fromString("_caldavs._tcp.example.com."),
DClass.IN, 3600, 10, 30, 8443, Name.fromString("dav1030.example.com.")
)
val records = arrayOf(dns1010, dns1020, dns1030)
val randomNumberGenerator = mockk<Random>()
for (i in 0..60) {
every { randomNumberGenerator.nextInt(0, 61) } returns i
val expected = when (i) {
in 0..10 -> dns1010
in 11..30 -> dns1020
else -> dns1030
}
assertEquals(expected, dnsRecordResolver.bestSRVRecord(records, randomNumberGenerator))
}
}
@Test
fun testBestSRVRecord_OneRecord() {
val dns1010 = SRVRecord(
Name.fromString("_caldavs._tcp.example.com."),
DClass.IN, 3600, 10, 10, 8443, Name.fromString("dav1010.example.com.")
)
val result = dnsRecordResolver.bestSRVRecord(arrayOf(dns1010))
assertEquals(dns1010, result)
}
@Test
fun testPathsFromTXTRecords_Empty() {
assertTrue(dnsRecordResolver.pathsFromTXTRecords(arrayOf()).isEmpty())
}
@Test
fun testPathsFromTXTRecords_OnePath() {
val result = dnsRecordResolver.pathsFromTXTRecords(arrayOf(
TXTRecord(Name.fromString("example.com."), 0, 0L, listOf("something=else", "path=/path1"))
)).toTypedArray()
assertArrayEquals(arrayOf("/path1"), result)
}
@Test
fun testPathsFromTXTRecords_TwoPaths() {
val result = dnsRecordResolver.pathsFromTXTRecords(arrayOf(
TXTRecord(Name.fromString("example.com."), 0, 0L, listOf("path=/path1", "something-else", "path=/path2"))
)).toTypedArray()
result.sort()
assertArrayEquals(arrayOf("/path1", "/path2"), result)
}
}

View File

@@ -1,30 +1,43 @@
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid
package at.bitfire.davdroid.network
import android.security.NetworkSecurityPolicy
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.test.runTest
import okhttp3.Request
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
import javax.inject.Provider
class HttpClientTest {
@HiltAndroidTest
class HttpClientBuilderTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Inject
lateinit var httpClientBuilder: Provider<HttpClientBuilder>
lateinit var server: MockWebServer
lateinit var httpClient: HttpClient
@Before
fun setUp() {
httpClient = HttpClient.Builder().build()
hiltRule.inject()
server = MockWebServer()
server.start(30000)
@@ -33,12 +46,25 @@ class HttpClientTest {
@After
fun tearDown() {
server.shutdown()
httpClient.close()
}
@Test
fun testBuildKtor_CreatesWorkingClient() = runTest {
server.enqueue(MockResponse()
.setResponseCode(200)
.setBody("Some Content"))
httpClientBuilder.get().buildKtor().use { client ->
val response = client.get(server.url("/").toString())
assertEquals(200, response.status.value)
assertEquals("Some Content", response.bodyAsText())
}
}
@Test
fun testCookies() {
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
val url = server.url("/test")
// set cookie for root path (/) and /test path in first response
@@ -47,7 +73,9 @@ class HttpClientTest {
.addHeader("Set-Cookie", "cookie1=1; path=/")
.addHeader("Set-Cookie", "cookie2=2")
.setBody("Cookie set"))
httpClient.okHttpClient.newCall(Request.Builder()
val httpClient = httpClientBuilder.get().build()
httpClient.newCall(Request.Builder()
.get().url(url)
.build()).execute()
assertNull(server.takeRequest().getHeader("Cookie"))
@@ -58,14 +86,15 @@ class HttpClientTest {
.addHeader("Set-Cookie", "cookie1=1a; path=/; Max-Age=0")
.addHeader("Set-Cookie", "cookie2=2a")
.setResponseCode(200))
httpClient.okHttpClient.newCall(Request.Builder()
httpClient.newCall(Request.Builder()
.get().url(url)
.build()).execute()
assertEquals("cookie2=2; cookie1=1", server.takeRequest().getHeader("Cookie"))
val header = server.takeRequest().getHeader("Cookie")
assertTrue(header == "cookie1=1; cookie2=2" || header == "cookie2=2; cookie1=1")
server.enqueue(MockResponse()
.setResponseCode(200))
httpClient.okHttpClient.newCall(Request.Builder()
httpClient.newCall(Request.Builder()
.get().url(url)
.build()).execute()
assertEquals("cookie2=2a", server.takeRequest().getHeader("Cookie"))

View File

@@ -0,0 +1,45 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import androidx.test.filters.SdkSuppress
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.Request
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class OkhttpClientTest {
@Inject
lateinit var httpClientBuilder: HttpClientBuilder
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Before
fun inject() {
hiltRule.inject()
}
@Test
@SdkSuppress(maxSdkVersion = 34)
fun testIcloudWithSettings() {
val client = httpClientBuilder.build()
client
.newCall(
Request.Builder()
.get()
.url("https://icloud.com")
.build()
)
.execute()
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.push
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class PushMessageHandlerTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var handler: PushMessageHandler
@Before
fun setUp() {
hiltRule.inject()
}
@Test
fun testParse_InvalidXml() {
Assert.assertNull(handler.parse("Non-XML content"))
}
@Test
fun testParse_WithXmlDeclAndTopic() {
val topic = handler.parse(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
"<P:push-message xmlns:D=\"DAV:\" xmlns:P=\"https://bitfire.at/webdav-push\">" +
" <P:topic>O7M1nQ7cKkKTKsoS_j6Z3w</P:topic>" +
"</P:push-message>"
)
Assert.assertEquals("O7M1nQ7cKkKTKsoS_j6Z3w", topic)
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.push
import android.content.Context
import android.content.Intent
import android.os.IBinder
import androidx.test.rule.ServiceTestRule
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.coVerify
import io.mockk.confirmVerified
import io.mockk.every
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit4.MockKRule
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.unifiedpush.android.connector.FailedReason
import org.unifiedpush.android.connector.PushService
import org.unifiedpush.android.connector.data.PushEndpoint
import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
@HiltAndroidTest
class UnifiedPushServiceTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockKRule = MockKRule(this)
@get:Rule
val serviceTestRule = ServiceTestRule()
@Inject
@ApplicationContext
lateinit var context: Context
@RelaxedMockK
@BindValue
lateinit var pushRegistrationManager: PushRegistrationManager
lateinit var binder: IBinder
lateinit var unifiedPushService: UnifiedPushService
@Before
fun setUp() {
hiltRule.inject()
binder = serviceTestRule.bindService(Intent(context, UnifiedPushService::class.java))!!
unifiedPushService = (binder as PushService.PushBinder).getService() as UnifiedPushService
}
@Test
fun testOnNewEndpoint() = runTest {
val endpoint = mockk<PushEndpoint> {
every { url } returns "https://example.com/12"
}
unifiedPushService.onNewEndpoint(endpoint, "12")
advanceUntilIdle()
coVerify {
pushRegistrationManager.processSubscription(12, endpoint)
}
confirmVerified(pushRegistrationManager)
}
@Test
fun testOnRegistrationFailed() = runTest {
unifiedPushService.onRegistrationFailed(FailedReason.INTERNAL_ERROR, "34")
advanceUntilIdle()
coVerify {
pushRegistrationManager.removeSubscription(34)
}
confirmVerified(pushRegistrationManager)
}
@Test
fun testOnUnregistered() = runTest {
unifiedPushService.onUnregistered("45")
advanceUntilIdle()
coVerify {
pushRegistrationManager.removeSubscription(45)
}
confirmVerified(pushRegistrationManager)
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.repository
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import androidx.hilt.work.HiltWorkerFactory
import at.bitfire.davdroid.R
import at.bitfire.davdroid.TestUtils
import at.bitfire.davdroid.resource.LocalAddressBookStore
import at.bitfire.davdroid.resource.LocalCalendarStore
import at.bitfire.davdroid.resource.LocalDataStore
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.sync.AutomaticSyncManager
import at.bitfire.davdroid.sync.SyncDataType
import at.bitfire.davdroid.sync.TasksAppManager
import at.bitfire.davdroid.sync.account.AccountsCleanupWorker
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.davdroid.sync.worker.SyncWorkerManager
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.clearAllMocks
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit4.MockKRule
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import io.mockk.verify
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class AccountRepositoryTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockKRule = MockKRule(this)
// System under test
@Inject
lateinit var accountRepository: AccountRepository
// Real injections
@Inject
@ApplicationContext
lateinit var context: Context
@Inject
lateinit var accountSettingsFactory: AccountSettings.Factory
@Inject
lateinit var workerFactory: HiltWorkerFactory
// Dependency overrides
@BindValue @MockK(relaxed = true)
lateinit var automaticSyncManager: AutomaticSyncManager
@BindValue @MockK(relaxed = true)
lateinit var localAddressBookStore: LocalAddressBookStore
@BindValue @MockK(relaxed = true)
lateinit var localCalendarStore: LocalCalendarStore
@BindValue @MockK(relaxed = true)
lateinit var serviceRepository: DavServiceRepository
@BindValue @MockK(relaxed = true)
lateinit var syncWorkerManager: SyncWorkerManager
@BindValue @MockK(relaxed = true)
lateinit var tasksAppManager: TasksAppManager
// Account setup
private val newName = "Renamed Account"
lateinit var am: AccountManager
lateinit var accountType: String
lateinit var account: Account
@Before
fun setUp() {
hiltRule.inject()
TestUtils.setUpWorkManager(context, workerFactory)
// Account setup
am = AccountManager.get(context)
accountType = context.getString(R.string.account_type)
account = TestAccount.create()
// AccountsCleanupWorker static mocking
mockkObject(AccountsCleanupWorker)
every { AccountsCleanupWorker.lockAccountsCleanup() } returns Unit
}
@After
fun tearDown() {
am.getAccountsByType(accountType).forEach { account ->
am.removeAccountExplicitly(account)
}
unmockkObject(AccountsCleanupWorker)
clearAllMocks()
}
// testRename
@Test(expected = IllegalArgumentException::class)
fun testRename_checksForAlreadyExisting() = runTest {
val existing = Account("Existing Account", accountType)
am.addAccountExplicitly(existing, null, null)
accountRepository.rename(account.name, existing.name)
}
@Test
fun testRename_locksAccountsCleanup() = runTest {
accountRepository.rename(account.name, newName)
verify { AccountsCleanupWorker.lockAccountsCleanup() }
}
@Test
fun testRename_renamesAccountInAndroid() = runTest {
accountRepository.rename(account.name, newName)
val accountsAfter = am.getAccountsByType(accountType)
assertTrue(accountsAfter.any { it.name == newName })
}
@Test
fun testRename_cancelsRunningSynchronizationOfOldAccount() = runTest {
accountRepository.rename(account.name, newName)
coVerify { syncWorkerManager.cancelAllWork(account) }
}
@Test
fun testRename_disablesPeriodicSyncsForOldAccount() = runTest {
accountRepository.rename(account.name, newName)
for (dataType in SyncDataType.entries)
coVerify(exactly = 1) {
syncWorkerManager.disablePeriodic(account, dataType)
}
}
@Test
fun testRename_updatesAccountNameReferencesInDatabase() = runTest {
accountRepository.rename(account.name, newName)
coVerify { serviceRepository.renameAccount(account.name, newName) }
}
@Test
fun testRename_updatesAddressBooks() = runTest {
accountRepository.rename(account.name, newName)
val newAccount = accountRepository.fromName(newName)
coVerify { localAddressBookStore.updateAccount(account, newAccount, any()) }
}
@Test
fun testRename_updatesCalendarEvents() = runTest {
accountRepository.rename(account.name, newName)
val newAccount = accountRepository.fromName(newName)
coVerify { localCalendarStore.updateAccount(account, newAccount, any()) }
}
@Test
fun testRename_updatesAccountNameOfLocalTasks() = runTest {
val mockDataStore = mockk<LocalDataStore<*>>(relaxed = true)
every { tasksAppManager.getDataStore() } returns mockDataStore
accountRepository.rename(account.name, newName)
val newAccount = accountRepository.fromName(newName)
coVerify { mockDataStore.updateAccount(account, newAccount, any()) }
}
@Test
fun testRename_updatesAutomaticSync() = runTest {
accountRepository.rename(account.name, newName)
val newAccount = accountRepository.fromName(newName)
coVerify { automaticSyncManager.updateAutomaticSync(newAccount) }
}
@Test
fun testRename_releasesAccountsCleanupWorkerMutex() = runTest {
accountRepository.rename(account.name, newName)
verify { AccountsCleanupWorker.lockAccountsCleanup() }
coVerify { serviceRepository.renameAccount(account.name, newName) }
}
}

View File

@@ -0,0 +1,225 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.accounts.Account
import android.accounts.AccountManager
import android.content.ContentProviderClient
import android.content.Context
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.sync.account.TestAccount
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.every
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit4.MockKRule
import io.mockk.mockk
import io.mockk.mockkObject
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class LocalAddressBookStoreTest {
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var localAddressBookStore: LocalAddressBookStore
@RelaxedMockK
lateinit var provider: ContentProviderClient
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockkRule = MockKRule(this)
lateinit var addressBookAccountType: String
lateinit var addressBookAccount: Account
lateinit var account: Account
lateinit var service: Service
@Before
fun setUp() {
hiltRule.inject()
addressBookAccountType = context.getString(R.string.account_type_address_book)
account = TestAccount.create()
service = Service(
id = 200,
accountName = account.name,
type = Service.Companion.TYPE_CARDDAV,
principal = null
)
db.serviceDao().insertOrReplace(service)
addressBookAccount = Account(
"MrRobert@example.com",
addressBookAccountType
)
}
@After
fun tearDown() {
TestAccount.remove(account)
removeAddressBooks()
}
@Test
fun test_accountName_removesSpecialChars() {
// Should remove iso control characters and `, ", ',
val collection = mockk<Collection> {
every { id } returns 1
every { url } returns "https://example.com/addressbook/funnyfriends".toHttpUrl()
every { displayName } returns "手 M's_\"F-e\"\\(´д`)/;æøå% äöü #42"
every { serviceId } returns service.id
}
assertEquals("手 Ms_F-e\\(´д)/;æøå% äöü #42 (Test Account) #1", localAddressBookStore.accountName(collection))
}
@Test
fun test_accountName_missingService() {
val collection = mockk<Collection> {
every { id } returns 42
every { url } returns "https://example.com/addressbook/funnyfriends".toHttpUrl()
every { displayName } returns null
every { serviceId } returns 404 // missing service
}
assertEquals("funnyfriends #42", localAddressBookStore.accountName(collection))
}
@Test
fun test_accountName_missingDisplayName() {
val collection = mockk<Collection> {
every { id } returns 42
every { url } returns "https://example.com/addressbook/funnyfriends".toHttpUrl()
every { displayName } returns null
every { serviceId } returns service.id
}
val accountName = localAddressBookStore.accountName(collection)
assertEquals("funnyfriends (${account.name}) #42", accountName)
}
@Test
fun test_accountName_missingDisplayNameAndService() {
val collection = mockk<Collection> {
every { id } returns 1
every { url } returns "https://example.com/addressbook/funnyfriends".toHttpUrl()
every { displayName } returns null
every { serviceId } returns 404 // missing service
}
assertEquals("funnyfriends #1", localAddressBookStore.accountName(collection))
}
@Test
fun test_create_createAccountReturnsNull() {
val collection = mockk<Collection>(relaxed = true) {
every { serviceId } returns service.id
every { id } returns 1
every { url } returns "https://example.com/addressbook/funnyfriends".toHttpUrl()
}
mockkObject(localAddressBookStore)
every { localAddressBookStore.createAddressBookAccount(any(), any(), any()) } returns null
assertEquals(null, localAddressBookStore.create(provider, collection))
}
@Test
fun test_create_ReadOnly() {
val collection = mockk<Collection>(relaxed = true) {
every { serviceId } returns service.id
every { id } returns 1
every { url } returns "https://example.com/addressbook/funnyfriends".toHttpUrl()
every { readOnly() } returns true
}
val addrBook = localAddressBookStore.create(provider, collection)!!
assertEquals(Account("funnyfriends (Test Account) #1", addressBookAccountType), addrBook.addressBookAccount)
assertTrue(addrBook.readOnly)
}
@Test
fun test_create_ReadWrite() {
val collection = mockk<Collection>(relaxed = true) {
every { serviceId } returns service.id
every { id } returns 1
every { url } returns "https://example.com/addressbook/funnyfriends".toHttpUrl()
every { readOnly() } returns false
}
val addrBook = localAddressBookStore.create(provider, collection)!!
assertEquals(Account("funnyfriends (Test Account) #1", addressBookAccountType), addrBook.addressBookAccount)
assertFalse(addrBook.readOnly)
}
@Test
fun test_getAll_differentAccount() {
val accountManager = AccountManager.get(context)
mockkObject(accountManager)
every { accountManager.getAccountsByType(any()) } returns arrayOf(addressBookAccount)
every { accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_NAME) } returns "Another Unrelated Account"
every { accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_TYPE) } returns account.type
val result = localAddressBookStore.getAll(account, provider)
assertTrue(result.isEmpty())
}
@Test
fun test_getAll_sameAccount() {
val accountManager = AccountManager.get(context)
mockkObject(accountManager)
every { accountManager.getAccountsByType(any()) } returns arrayOf(addressBookAccount)
every { accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_NAME) } returns account.name
every { accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_TYPE) } returns account.type
val result = localAddressBookStore.getAll(account, provider)
assertEquals(1, result.size)
assertEquals(addressBookAccount, result.first().addressBookAccount)
}
/**
* Tests the calculation of read only state is correct
*/
@Test
fun test_shouldBeReadOnly() {
val collectionReadOnly = mockk<Collection> { every { readOnly() } returns true }
assertTrue(LocalAddressBookStore.shouldBeReadOnly(collectionReadOnly, false))
assertTrue(LocalAddressBookStore.shouldBeReadOnly(collectionReadOnly, true))
val collectionNotReadOnly = mockk<Collection> { every { readOnly() } returns false }
assertFalse(LocalAddressBookStore.shouldBeReadOnly(collectionNotReadOnly, false))
assertTrue(LocalAddressBookStore.shouldBeReadOnly(collectionNotReadOnly, true))
}
// helpers
private fun removeAddressBooks() {
val accountManager = AccountManager.get(context)
accountManager.getAccountsByType(addressBookAccountType).forEach {
accountManager.removeAccountExplicitly(it)
}
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.Manifest
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentUris
import android.content.Context
import android.provider.ContactsContract
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import at.bitfire.vcard4android.Contact
import at.bitfire.vcard4android.LabeledProperty
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import ezvcard.property.Telephone
import org.junit.AfterClass
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import java.io.FileNotFoundException
import java.util.LinkedList
import java.util.Optional
import javax.inject.Inject
@HiltAndroidTest
class LocalAddressBookTest {
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var localTestAddressBookProvider: LocalTestAddressBookProvider
@get:Rule
val hiltRule = HiltAndroidRule(this)
val account = Account("Test Account", "Test Account Type")
@Before
fun setUp() {
hiltRule.inject()
}
/**
* Tests whether contacts are moved (and not lost) when an address book is renamed.
*/
@Test
fun test_renameAccount_retainsContacts() {
localTestAddressBookProvider.provide(account, provider) { addressBook ->
// insert contact with data row
val uid = "12345"
val contact = Contact(
uid = uid,
displayName = "Test Contact",
phoneNumbers = LinkedList(listOf(LabeledProperty(Telephone("1234567890"))))
)
val uri = LocalContact(addressBook, contact, null, null, 0).add()
val id = ContentUris.parseId(uri)
val localContact = addressBook.findContactById(id)
localContact.resetDirty()
assertFalse("Contact is dirty before moving", isContactDirty(addressBook, id))
// rename address book
val newName = "New Name"
addressBook.renameAccount(newName)
assertEquals(newName, addressBook.addressBookAccount.name)
// check whether contact is still here (including data rows) and not dirty
val result = addressBook.findContactById(id)
assertFalse("Contact is dirty after moving", isContactDirty(addressBook, id))
val contact2 = result.getContact()
assertEquals(uid, contact2.uid)
assertEquals("Test Contact", contact2.displayName)
assertEquals("1234567890", contact2.phoneNumbers.first().component1().text)
}
}
/**
* Tests whether groups are moved (and not lost) when an address book is renamed.
*/
@Test
fun test_renameAccount_retainsGroups() {
localTestAddressBookProvider.provide(account, provider) { addressBook ->
// insert group
val localGroup = LocalGroup(addressBook, Contact(displayName = "Test Group"), null, null, 0)
val uri = localGroup.add()
val id = ContentUris.parseId(uri)
// make sure it's not dirty
localGroup.clearDirty(Optional.empty(), null, null)
assertFalse("Group is dirty before moving", isGroupDirty(addressBook, id))
// rename address book
val newName = "New Name"
assertTrue(addressBook.renameAccount(newName))
assertEquals(newName, addressBook.addressBookAccount.name)
// check whether group is still here and not dirty
val result = addressBook.findGroupById(id)
assertFalse("Group is dirty after moving", isGroupDirty(addressBook, id))
val group = result.getContact()
assertEquals("Test Group", group.displayName)
}
}
// helpers
/**
* Returns the dirty flag of the given contact.
*
* @return true if the contact is dirty, false otherwise
*
* @throws FileNotFoundException if the contact can't be found
*/
fun isContactDirty(adddressBook: LocalAddressBook, id: Long): Boolean {
val uri = ContentUris.withAppendedId(adddressBook.rawContactsSyncUri(), id)
provider.query(uri, arrayOf(ContactsContract.RawContacts.DIRTY), null, null, null)?.use { cursor ->
if (cursor.moveToFirst())
return cursor.getInt(0) != 0
}
throw FileNotFoundException()
}
/**
* Returns the dirty flag of the given contact group.
*
* @return true if the group is dirty, false otherwise
*
* @throws FileNotFoundException if the group can't be found
*/
fun isGroupDirty(adddressBook: LocalAddressBook, id: Long): Boolean {
val uri = ContentUris.withAppendedId(adddressBook.groupsSyncUri(), id)
provider.query(uri, arrayOf(ContactsContract.Groups.DIRTY), null, null, null)?.use { cursor ->
if (cursor.moveToFirst())
return cursor.getInt(0) != 0
}
throw FileNotFoundException()
}
companion object {
@JvmField
@ClassRule
val permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)!!
private lateinit var provider: ContentProviderClient
@BeforeClass
@JvmStatic
fun connect() {
val context = InstrumentationRegistry.getInstrumentation().context
provider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)!!
}
@AfterClass
@JvmStatic
fun disconnect() {
provider.close()
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.Context
import android.net.Uri
import android.provider.CalendarContract
import android.provider.CalendarContract.Calendars
import androidx.core.content.contentValuesOf
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.test.InitCalendarProviderRule
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import javax.inject.Inject
@HiltAndroidTest
class LocalCalendarStoreTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val initCalendarProviderRule: TestRule = InitCalendarProviderRule.initialize()
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var localCalendarStore: LocalCalendarStore
private lateinit var provider: ContentProviderClient
private lateinit var account: Account
private lateinit var calendarUri: Uri
@Before
fun setUp() {
hiltRule.inject()
provider = context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
account = TestAccount.create(accountName = "InitialAccountName")
calendarUri = createCalendarForAccount(account)
}
@After
fun tearDown() {
provider.delete(calendarUri, null, null)
TestAccount.remove(account)
provider.closeCompat()
}
@Test
fun testUpdateAccount_updatesOwnerAccount() {
// Verify initial state (assume to skip and prevent flaky test failures)
Assume.assumeTrue("InitialAccountName" == getOwnerAccount())
// Rename account
val oldAccount = account
account = TestAccount.rename(account, "ChangedAccountName")
// Update account name in local calendar
localCalendarStore.updateAccount(oldAccount, account, provider)
// Verify [Calendar.OWNER_ACCOUNT] of local calendar was updated
assertEquals("ChangedAccountName", getOwnerAccount())
}
// helpers
private fun createCalendarForAccount(account: Account): Uri =
provider.insert(
Calendars.CONTENT_URI.asSyncAdapter(account),
contentValuesOf(
Calendars.ACCOUNT_NAME to account.name,
Calendars.ACCOUNT_TYPE to account.type,
Calendars.OWNER_ACCOUNT to account.name,
Calendars.VISIBLE to 1,
Calendars.SYNC_EVENTS to 1,
Calendars._SYNC_ID to 999,
Calendars.CALENDAR_DISPLAY_NAME to "displayName",
)
)!!.asSyncAdapter(account)
private fun getOwnerAccount(): String {
provider.query(
calendarUri,
arrayOf(Calendars.OWNER_ACCOUNT),
"${Calendars.ACCOUNT_NAME}=?",
arrayOf(account.name),
null
)!!.use { cursor ->
cursor.moveToNext()
return cursor.getString(0)
}
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentValues
import android.content.Entity
import android.provider.CalendarContract
import android.provider.CalendarContract.ACCOUNT_TYPE_LOCAL
import android.provider.CalendarContract.Events
import androidx.core.content.contentValuesOf
import androidx.test.platform.app.InstrumentationRegistry
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.storage.calendar.AndroidCalendar
import at.bitfire.synctools.storage.calendar.AndroidCalendarProvider
import at.bitfire.synctools.storage.calendar.EventsContract
import at.bitfire.synctools.test.InitCalendarProviderRule
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import javax.inject.Inject
@HiltAndroidTest
class LocalCalendarTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val initCalendarProviderRule: TestRule = InitCalendarProviderRule.initialize()
@Inject
lateinit var localCalendarFactory: LocalCalendar.Factory
private val account = Account("LocalCalendarTest", ACCOUNT_TYPE_LOCAL)
private lateinit var androidCalendar: AndroidCalendar
private lateinit var client: ContentProviderClient
private lateinit var calendar: LocalCalendar
@Before
fun setUp() {
hiltRule.inject()
val context = InstrumentationRegistry.getInstrumentation().targetContext
client = context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
val provider = AndroidCalendarProvider(account, client)
androidCalendar = provider.createAndGetCalendar(ContentValues())
calendar = localCalendarFactory.create(androidCalendar)
}
@After
fun tearDown() {
androidCalendar.delete()
client.closeCompat()
}
/**
* Verifies that [LocalCalendar.removeNotDirtyMarked] works as expected.
* @param contentValues values to set on the event. Required:
* - [Events._ID]
* - [Events.DIRTY]
*/
private fun testRemoveNotDirtyMarked(contentValues: ContentValues) {
val entity = Entity(
contentValuesOf(
Events.CALENDAR_ID to androidCalendar.id,
Events.DTSTART to System.currentTimeMillis(),
Events.DTEND to System.currentTimeMillis(),
Events.TITLE to "Some Event",
EventsContract.COLUMN_FLAGS to 123
).apply { putAll(contentValues) }
)
val id = androidCalendar.addEvent(entity)
calendar.removeNotDirtyMarked(123)
assertNull(androidCalendar.getEvent(id))
}
@Test
fun testRemoveNotDirtyMarked_IdLargerThanIntMaxValue() = testRemoveNotDirtyMarked(
contentValuesOf(Events._ID to Int.MAX_VALUE.toLong() + 10, Events.DIRTY to 0)
)
@Test
fun testRemoveNotDirtyMarked_DirtyIs0() = testRemoveNotDirtyMarked(
contentValuesOf(Events._ID to 1, Events.DIRTY to 0)
)
@Test
fun testRemoveNotDirtyMarked_DirtyNull() = testRemoveNotDirtyMarked(
contentValuesOf(Events._ID to 1, Events.DIRTY to null)
)
/**
* Verifies that [LocalCalendar.markNotDirty] works as expected.
* @param contentValues values to set on the event. Required:
* - [Events.DIRTY]
*/
private fun testMarkNotDirty(contentValues: ContentValues) {
val id = androidCalendar.addEvent(Entity(
contentValuesOf(
Events.CALENDAR_ID to androidCalendar.id,
Events._ID to 1,
Events.DTSTART to System.currentTimeMillis(),
Events.DTEND to System.currentTimeMillis(),
Events.TITLE to "Some Event",
EventsContract.COLUMN_FLAGS to 123
).apply { putAll(contentValues) }
))
val updated = calendar.markNotDirty(321)
assertEquals(1, updated)
assertEquals(321, androidCalendar.getEvent(id)?.entityValues?.getAsInteger(EventsContract.COLUMN_FLAGS))
}
@Test
fun test_markNotDirty_DirtyIs0() = testMarkNotDirty(
contentValuesOf(
Events.DIRTY to 0
)
)
@Test
fun test_markNotDirty_DirtyIsNull() = testMarkNotDirty(
contentValuesOf(
Events.DIRTY to null
)
)
}

View File

@@ -0,0 +1,263 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.Manifest
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds.GroupMembership
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import at.bitfire.synctools.storage.ContactsBatchOperation
import at.bitfire.vcard4android.CachedGroupMembership
import at.bitfire.vcard4android.Contact
import at.bitfire.vcard4android.GroupMethod
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.Optional
import javax.inject.Inject
@HiltAndroidTest
class LocalGroupTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)!!
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var localTestAddressBookProvider: LocalTestAddressBookProvider
lateinit var provider: ContentProviderClient
val account = Account("Test Account", "Test Account Type")
@Before
fun setUp() {
hiltRule.inject()
val context = InstrumentationRegistry.getInstrumentation().context
provider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)!!
}
@After
fun tearDown() {
provider.close()
}
@Test
fun testApplyPendingMemberships_addPendingMembership() {
localTestAddressBookProvider.provide(account, provider, GroupMethod.GROUP_VCARDS) { ab ->
val contact1 = LocalContact(ab, Contact().apply {
uid = "test1"
displayName = "Test"
}, "test1.vcf", null, 0)
contact1.add()
val group = newGroup(ab)
// set pending membership of contact1
ab.provider!!.update(
ContentUris.withAppendedId(ab.groupsSyncUri(), group.id!!),
ContentValues().apply {
put(LocalGroup.COLUMN_PENDING_MEMBERS, LocalGroup.PendingMemberships(setOf("test1")).toString())
},
null, null
)
// pending membership -> contact1 should be added to group
LocalGroup.applyPendingMemberships(ab)
// check group membership
ab.provider!!.query(
ab.syncAdapterURI(ContactsContract.Data.CONTENT_URI), arrayOf(GroupMembership.GROUP_ROW_ID, GroupMembership.RAW_CONTACT_ID),
"${GroupMembership.MIMETYPE}=?", arrayOf(GroupMembership.CONTENT_ITEM_TYPE),
null
)!!.use { cursor ->
assertTrue(cursor.moveToNext())
assertEquals(group.id, cursor.getLong(0))
assertEquals(contact1.id, cursor.getLong(1))
assertFalse(cursor.moveToNext())
}
// check cached group membership
ab.provider!!.query(
ab.syncAdapterURI(ContactsContract.Data.CONTENT_URI), arrayOf(CachedGroupMembership.GROUP_ID, CachedGroupMembership.RAW_CONTACT_ID),
"${CachedGroupMembership.MIMETYPE}=?", arrayOf(CachedGroupMembership.CONTENT_ITEM_TYPE),
null
)!!.use { cursor ->
assertTrue(cursor.moveToNext())
assertEquals(group.id, cursor.getLong(0))
assertEquals(contact1.id, cursor.getLong(1))
assertFalse(cursor.moveToNext())
}
}
}
@Test
fun testApplyPendingMemberships_removeMembership() {
localTestAddressBookProvider.provide(account, provider, GroupMethod.GROUP_VCARDS) { ab ->
val contact1 = LocalContact(ab, Contact().apply {
uid = "test1"
displayName = "Test"
}, "test1.vcf", null, 0)
contact1.add()
val group = newGroup(ab)
// add contact1 to group
val batch = ContactsBatchOperation(ab.provider!!)
contact1.addToGroup(batch, group.id!!)
batch.commit()
// no pending memberships -> membership should be removed
LocalGroup.applyPendingMemberships(ab)
// check group membership
ab.provider!!.query(
ab.syncAdapterURI(ContactsContract.Data.CONTENT_URI),
arrayOf(GroupMembership.GROUP_ROW_ID, GroupMembership.RAW_CONTACT_ID),
"${GroupMembership.MIMETYPE}=?",
arrayOf(GroupMembership.CONTENT_ITEM_TYPE),
null
)!!.use { cursor ->
assertFalse(cursor.moveToNext())
}
// check cached group membership
ab.provider!!.query(
ab.syncAdapterURI(ContactsContract.Data.CONTENT_URI),
arrayOf(CachedGroupMembership.GROUP_ID, CachedGroupMembership.RAW_CONTACT_ID),
"${CachedGroupMembership.MIMETYPE}=?",
arrayOf(CachedGroupMembership.CONTENT_ITEM_TYPE),
null
)!!.use { cursor ->
assertFalse(cursor.moveToNext())
}
}
}
@Test
fun testClearDirty_addCachedGroupMembership() {
localTestAddressBookProvider.provide(account, provider, GroupMethod.CATEGORIES) { ab ->
val group = newGroup(ab)
val contact1 =
LocalContact(ab, Contact().apply { displayName = "Test" }, "fn.vcf", null, 0)
contact1.add()
// insert group membership, but no cached group membership
ab.provider!!.insert(
ab.syncAdapterURI(ContactsContract.Data.CONTENT_URI), ContentValues().apply {
put(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE)
put(GroupMembership.RAW_CONTACT_ID, contact1.id)
put(GroupMembership.GROUP_ROW_ID, group.id)
}
)
group.clearDirty(Optional.empty(), null)
// check cached group membership
ab.provider!!.query(
ab.syncAdapterURI(ContactsContract.Data.CONTENT_URI),
arrayOf(CachedGroupMembership.GROUP_ID, CachedGroupMembership.RAW_CONTACT_ID),
"${CachedGroupMembership.MIMETYPE}=?",
arrayOf(CachedGroupMembership.CONTENT_ITEM_TYPE),
null
)!!.use { cursor ->
assertTrue(cursor.moveToNext())
assertEquals(group.id, cursor.getLong(0))
assertEquals(contact1.id, cursor.getLong(1))
assertFalse(cursor.moveToNext())
}
}
}
@Test
fun testClearDirty_removeCachedGroupMembership() {
localTestAddressBookProvider.provide(account, provider, GroupMethod.CATEGORIES) { ab ->
val group = newGroup(ab)
val contact1 = LocalContact(ab, Contact().apply { displayName = "Test" }, "fn.vcf", null, 0)
contact1.add()
// insert cached group membership, but no group membership
ab.provider!!.insert(
ab.syncAdapterURI(ContactsContract.Data.CONTENT_URI), ContentValues().apply {
put(CachedGroupMembership.MIMETYPE, CachedGroupMembership.CONTENT_ITEM_TYPE)
put(CachedGroupMembership.RAW_CONTACT_ID, contact1.id)
put(CachedGroupMembership.GROUP_ID, group.id)
}
)
group.clearDirty(Optional.empty(), null)
// cached group membership should be gone
ab.provider!!.query(
ab.syncAdapterURI(ContactsContract.Data.CONTENT_URI), arrayOf(CachedGroupMembership.GROUP_ID, CachedGroupMembership.RAW_CONTACT_ID),
"${CachedGroupMembership.MIMETYPE}=?", arrayOf(CachedGroupMembership.CONTENT_ITEM_TYPE),
null
)!!.use { cursor ->
assertFalse(cursor.moveToNext())
}
}
}
@Test
fun testMarkMembersDirty() {
localTestAddressBookProvider.provide(account, provider, GroupMethod.CATEGORIES) { ab ->
val group = newGroup(ab)
val contact1 =
LocalContact(ab, Contact().apply { displayName = "Test" }, "fn.vcf", null, 0)
contact1.add()
val batch = ContactsBatchOperation(ab.provider!!)
contact1.addToGroup(batch, group.id!!)
batch.commit()
assertEquals(0, ab.findDirty().size)
group.markMembersDirty()
assertEquals(contact1.id, ab.findDirty().first().id)
}
}
@Test
fun testUpdate() {
localTestAddressBookProvider.provide(account, provider) { ab ->
val group = newGroup(ab)
group.update(Contact(displayName = "New Group Name"), null, null, null, 0)
}
}
// helpers
private fun newGroup(addressBook: LocalAddressBook): LocalGroup =
LocalGroup(addressBook,
Contact().apply {
displayName = "Test Group"
}, null, null, 0
).apply {
add()
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.Context
import at.bitfire.davdroid.repository.DavCollectionRepository
import at.bitfire.davdroid.repository.DavServiceRepository
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.sync.adapter.SyncFrameworkIntegration
import at.bitfire.vcard4android.GroupMethod
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.Optional
import java.util.logging.Logger
/**
* A local address book that provides an easy way to set the group method in tests.
*/
class LocalTestAddressBook @AssistedInject constructor(
@Assisted account: Account,
@Assisted("addressBook") addressBookAccount: Account,
@Assisted provider: ContentProviderClient,
@Assisted override val groupMethod: GroupMethod,
accountSettingsFactory: AccountSettings.Factory,
collectionRepository: DavCollectionRepository,
@ApplicationContext context: Context,
logger: Logger,
serviceRepository: DavServiceRepository,
syncFramework: SyncFrameworkIntegration
): LocalAddressBook(
account = account,
_addressBookAccount = addressBookAccount,
provider = provider,
accountSettingsFactory = accountSettingsFactory,
collectionRepository = collectionRepository,
context = context,
dirtyVerifier = Optional.empty(),
logger = logger,
serviceRepository = serviceRepository,
syncFramework = syncFramework
) {
@AssistedFactory
interface Factory {
fun create(
account: Account,
@Assisted("addressBook") addressBookAccount: Account,
provider: ContentProviderClient,
groupMethod: GroupMethod
): LocalTestAddressBook
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource
import android.accounts.Account
import android.accounts.AccountManager
import android.content.ContentProviderClient
import android.content.Context
import at.bitfire.davdroid.R
import at.bitfire.vcard4android.GroupMethod
import dagger.hilt.android.qualifiers.ApplicationContext
import org.junit.Assert.assertTrue
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
/**
* Provides [LocalTestAddressBook]s in tests.
*/
class LocalTestAddressBookProvider @Inject constructor(
@ApplicationContext context: Context,
private val localTestAddressBookFactory: LocalTestAddressBook.Factory
) {
/**
* Counter for creating unique address book names.
*/
val counter = AtomicInteger()
val accountManager = AccountManager.get(context)
val accountType = context.getString(R.string.account_type_address_book)
/**
* Creates and provides a new temporary [LocalTestAddressBook] for the given [account] and
* removes it again.
*
* @param account The DAVx5 account to use for the address book
* @param provider Content provider needed to access and modify the address book
* @param groupMethod The group method the address book should use
* @param block Function to execute with the temporary available address book
*/
fun provide(
account: Account,
provider: ContentProviderClient,
groupMethod: GroupMethod = GroupMethod.GROUP_VCARDS,
block: (LocalTestAddressBook) -> Unit
) {
// create new address book account
val addressBookAccount = Account("Test Address Book ${counter.incrementAndGet()}", accountType)
assertTrue(accountManager.addAccountExplicitly(addressBookAccount, null, null))
val addressBook = localTestAddressBookFactory.create(account, addressBookAccount, provider, groupMethod)
// Empty the address book (Needed by LocalGroupTest)
for (contact in addressBook.queryContacts(null, null))
contact.delete()
for (group in addressBook.queryGroups(null, null))
group.delete()
try {
// provide address book
block(addressBook)
} finally {
// recreate account of provided address book, since the account might have been renamed
val renamedAccount = Account(addressBook.addressBookAccount.name, addressBook.addressBookAccount.type)
// remove address book account / address book
assertTrue(accountManager.removeAccountExplicitly(renamedAccount))
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource.contactrow
import android.Manifest
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentValues
import android.content.Context
import android.provider.ContactsContract
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import at.bitfire.davdroid.resource.LocalContact
import at.bitfire.davdroid.resource.LocalTestAddressBookProvider
import at.bitfire.vcard4android.CachedGroupMembership
import at.bitfire.vcard4android.Contact
import at.bitfire.vcard4android.GroupMethod
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.AfterClass
import org.junit.Assert.assertArrayEquals
import org.junit.Before
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class CachedGroupMembershipHandlerTest {
@Inject
@ApplicationContext
lateinit var context: Context
@Inject
lateinit var localTestAddressBookProvider: LocalTestAddressBookProvider
@get:Rule
val hiltRule = HiltAndroidRule(this)
val account = Account("Test Account", "Test Account Type")
@Before
fun inject() {
hiltRule.inject()
}
@Test
fun testMembership() {
localTestAddressBookProvider.provide(account, provider, GroupMethod.GROUP_VCARDS) { addressBook ->
val contact = Contact()
val localContact = LocalContact(addressBook, contact, null, null, 0)
CachedGroupMembershipHandler(localContact).handle(ContentValues().apply {
put(CachedGroupMembership.GROUP_ID, 123456)
put(CachedGroupMembership.RAW_CONTACT_ID, 789)
}, contact)
assertArrayEquals(arrayOf(123456L), localContact.cachedGroupMemberships.toArray())
}
}
companion object {
@JvmField
@ClassRule
val permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)!!
private lateinit var provider: ContentProviderClient
@BeforeClass
@JvmStatic
fun connect() {
val context = InstrumentationRegistry.getInstrumentation().context
provider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)!!
}
@AfterClass
@JvmStatic
fun disconnect() {
provider.close()
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource.contactrow
import android.Manifest
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.Context
import android.net.Uri
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds.GroupMembership
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import at.bitfire.davdroid.resource.LocalTestAddressBookProvider
import at.bitfire.vcard4android.Contact
import at.bitfire.vcard4android.GroupMethod
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.AfterClass
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class GroupMembershipBuilderTest {
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var localTestAddressBookProvider: LocalTestAddressBookProvider
@get:Rule
val hiltRule = HiltAndroidRule(this)
val account = Account("Test Account", "Test Account Type")
@Before
fun inject() {
hiltRule.inject()
}
@Test
fun testCategories_GroupsAsCategories() {
val contact = Contact().apply {
categories += "TEST GROUP"
}
localTestAddressBookProvider.provide(account, provider, GroupMethod.CATEGORIES) { addressBookGroupsAsCategories ->
GroupMembershipBuilder(Uri.EMPTY, null, contact, addressBookGroupsAsCategories, false).build().also { result ->
assertEquals(1, result.size)
assertEquals(GroupMembership.CONTENT_ITEM_TYPE, result[0].values[GroupMembership.MIMETYPE])
assertEquals(addressBookGroupsAsCategories.findOrCreateGroup("TEST GROUP"), result[0].values[GroupMembership.GROUP_ROW_ID])
}
}
}
@Test
fun testCategories_GroupsAsVCards() {
val contact = Contact().apply {
categories += "TEST GROUP"
}
localTestAddressBookProvider.provide(account, provider, GroupMethod.GROUP_VCARDS) { addressBookGroupsAsVCards ->
GroupMembershipBuilder(Uri.EMPTY, null, contact, addressBookGroupsAsVCards, false).build().also { result ->
// group membership is constructed during post-processing
assertEquals(0, result.size)
}
}
}
companion object {
@JvmField
@ClassRule
val permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)!!
private lateinit var provider: ContentProviderClient
@BeforeClass
@JvmStatic
fun connect() {
val context: Context = InstrumentationRegistry.getInstrumentation().context
provider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)!!
}
@AfterClass
@JvmStatic
fun disconnect() {
provider.close()
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource.contactrow
import android.Manifest
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentValues
import android.content.Context
import android.provider.ContactsContract
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import at.bitfire.davdroid.resource.LocalContact
import at.bitfire.davdroid.resource.LocalTestAddressBookProvider
import at.bitfire.vcard4android.CachedGroupMembership
import at.bitfire.vcard4android.Contact
import at.bitfire.vcard4android.GroupMethod
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.AfterClass
import org.junit.Assert
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class GroupMembershipHandlerTest {
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var localTestAddressBookProvider: LocalTestAddressBookProvider
@get:Rule
var hiltRule = HiltAndroidRule(this)
val account = Account("Test Account", "Test Account Type")
@Before
fun inject() {
hiltRule.inject()
}
@Test
fun testMembership_GroupsAsCategories() {
localTestAddressBookProvider.provide(account, provider, GroupMethod.CATEGORIES) { addressBookGroupsAsCategories ->
val addressBookGroupsAsCategoriesGroup = addressBookGroupsAsCategories.findOrCreateGroup("TEST GROUP")
val contact = Contact()
val localContact = LocalContact(addressBookGroupsAsCategories, contact, null, null, 0)
GroupMembershipHandler(localContact).handle(ContentValues().apply {
put(CachedGroupMembership.GROUP_ID, addressBookGroupsAsCategoriesGroup)
put(CachedGroupMembership.RAW_CONTACT_ID, -1)
}, contact)
assertArrayEquals(arrayOf(addressBookGroupsAsCategoriesGroup), localContact.groupMemberships.toArray())
assertArrayEquals(arrayOf("TEST GROUP"), contact.categories.toArray())
}
}
@Test
fun testMembership_GroupsAsVCards() {
localTestAddressBookProvider.provide(account, provider, GroupMethod.GROUP_VCARDS) { addressBookGroupsAsVCards ->
val contact = Contact()
val localContact = LocalContact(addressBookGroupsAsVCards, contact, null, null, 0)
GroupMembershipHandler(localContact).handle(ContentValues().apply {
put(CachedGroupMembership.GROUP_ID, 12345) // because the group name is not queried and put into CATEGORIES, the group doesn't have to really exist
put(CachedGroupMembership.RAW_CONTACT_ID, -1)
}, contact)
assertArrayEquals(arrayOf(12345L), localContact.groupMemberships.toArray())
assertTrue(contact.categories.isEmpty())
}
}
companion object {
@JvmField
@ClassRule
val permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)!!
private lateinit var provider: ContentProviderClient
@BeforeClass
@JvmStatic
fun connect() {
val context: Context = InstrumentationRegistry.getInstrumentation().context
provider = context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY)!!
Assert.assertNotNull(provider)
}
@AfterClass
@JvmStatic
fun disconnect() {
provider.close()
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource.contactrow
import android.net.Uri
import at.bitfire.vcard4android.Contact
import org.junit.Assert.assertEquals
import org.junit.Test
class UnknownPropertiesBuilderTest {
@Test
fun testUnknownProperties_None() {
UnknownPropertiesBuilder(Uri.EMPTY, null, Contact(), false).build().also { result ->
assertEquals(0, result.size)
}
}
@Test
fun testUnknownProperties_Properties() {
UnknownPropertiesBuilder(Uri.EMPTY, null, Contact().apply {
unknownProperties = "X-TEST:12345"
}, false).build().also { result ->
assertEquals(1, result.size)
assertEquals(UnknownProperties.CONTENT_ITEM_TYPE, result[0].values[UnknownProperties.MIMETYPE])
assertEquals("X-TEST:12345", result[0].values[UnknownProperties.UNKNOWN_PROPERTIES])
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.resource.contactrow
import android.content.ContentValues
import at.bitfire.vcard4android.Contact
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class UnknownPropertiesHandlerTest {
@Test
fun testUnknownProperties_Empty() {
val contact = Contact()
UnknownPropertiesHandler.handle(ContentValues().apply {
putNull(UnknownProperties.UNKNOWN_PROPERTIES)
}, contact)
assertNull(contact.unknownProperties)
}
@Test
fun testUnknownProperties_Values() {
val contact = Contact()
UnknownPropertiesHandler.handle(ContentValues().apply {
put(UnknownProperties.UNKNOWN_PROPERTIES, "X-TEST:12345")
}, contact)
assertEquals("X-TEST:12345", contact.unknownProperties)
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.servicedetection
import android.security.NetworkSecurityPolicy
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.network.HttpClientBuilder
import at.bitfire.davdroid.settings.SettingsManager
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.impl.annotations.MockK
import io.mockk.junit4.MockKRule
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.logging.Logger
import javax.inject.Inject
@HiltAndroidTest
class CollectionsWithoutHomeSetRefresherTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockKRule = MockKRule(this)
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var httpClientBuilder: HttpClientBuilder
@Inject
lateinit var logger: Logger
@Inject
lateinit var refresherFactory: CollectionsWithoutHomeSetRefresher.Factory
@BindValue
@MockK(relaxed = true)
lateinit var settings: SettingsManager
private lateinit var client: OkHttpClient
private lateinit var mockServer: MockWebServer
private lateinit var service: Service
@Before
fun setUp() {
hiltRule.inject()
// Start mock web server
mockServer = MockWebServer().apply {
dispatcher = TestDispatcher(logger)
start()
}
// build HTTP client
client = httpClientBuilder.build()
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
// insert test service
val serviceId = db.serviceDao().insertOrReplace(
Service(id = 0, accountName = "test", type = Service.TYPE_CARDDAV, principal = null)
)
service = db.serviceDao().get(serviceId)!!
}
@After
fun tearDown() {
mockServer.shutdown()
}
// refreshCollectionsWithoutHomeSet
@Test
fun refreshCollectionsWithoutHomeSet_updatesExistingCollection() {
// place homeless collection in DB
val collectionId = db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
null,
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"),
)
)
// Refresh
refresherFactory.create(service, client).refreshCollectionsWithoutHomeSet()
// Check the collection got updated - with display name and description
assertEquals(
Collection(
collectionId,
service.id,
null,
1, // will have gotten an owner too
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"),
displayName = "My Contacts",
description = "My Contacts Description"
),
db.collectionDao().get(collectionId)
)
}
@Test
fun refreshCollectionsWithoutHomeSet_deletesInaccessibleCollectionsWithoutHomeSet() {
// place homeless collection in DB - it is also inaccessible
val collectionId = db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
null,
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_INACCESSIBLE")
)
)
// Refresh - should delete collection
refresherFactory.create(service, client).refreshCollectionsWithoutHomeSet()
// Check the collection got deleted
assertEquals(null, db.collectionDao().get(collectionId))
}
@Test
fun refreshCollectionsWithoutHomeSet_addsOwnerUrls() {
// place homeless collection in DB
val collectionId = db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
null,
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"),
)
)
// Refresh homeless collections
assertEquals(0, db.principalDao().getByService(service.id).size)
refresherFactory.create(service, client).refreshCollectionsWithoutHomeSet()
// Check principal saved and the collection was updated with its reference
val principals = db.principalDao().getByService(service.id)
assertEquals(1, principals.size)
assertEquals(mockServer.url("$PATH_CARDDAV$SUBPATH_PRINCIPAL"), principals[0].url)
assertEquals(null, principals[0].displayName)
assertEquals(
principals[0].id,
db.collectionDao().get(collectionId)!!.ownerId
)
}
companion object {
private const val PATH_CARDDAV = "/carddav"
private const val SUBPATH_PRINCIPAL = "/principal"
private const val SUBPATH_ADDRESSBOOK = "/addressbooks/my-contacts"
private const val SUBPATH_ADDRESSBOOK_INACCESSIBLE = "/addressbooks/inaccessible-contacts"
}
class TestDispatcher(
private val logger: Logger
): Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val path = request.path!!.trimEnd('/')
logger.info("${request.method} on $path")
if (request.method.equals("PROPFIND", true)) {
val properties = when (path) {
PATH_CARDDAV + SUBPATH_ADDRESSBOOK ->
"<resourcetype>" +
" <collection/>" +
" <CARD:addressbook/>" +
"</resourcetype>" +
"<displayname>My Contacts</displayname>" +
"<CARD:addressbook-description>My Contacts Description</CARD:addressbook-description>" +
"<owner>" +
" <href>${PATH_CARDDAV + SUBPATH_PRINCIPAL}</href>" +
"</owner>"
else -> ""
}
return MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav' xmlns:CAL='urn:ietf:params:xml:ns:caldav'>" +
"<response>" +
" <href>$path</href>" +
" <propstat><prop>"+
properties +
" </prop></propstat>" +
"</response>" +
"</multistatus>")
}
return MockResponse().setResponseCode(404)
}
}
}

View File

@@ -1,32 +1,39 @@
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.ui.setup
package at.bitfire.davdroid.servicedetection
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.property.AddressbookHomeSet
import at.bitfire.dav4jvm.property.ResourceType
import at.bitfire.davdroid.HttpClient
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.Credentials
import at.bitfire.davdroid.ui.setup.DavResourceFinder.Configuration.ServiceInfo
import android.security.NetworkSecurityPolicy
import at.bitfire.dav4jvm.okhttp.DavResource
import at.bitfire.dav4jvm.property.carddav.CardDAV
import at.bitfire.dav4jvm.property.webdav.WebDAV
import at.bitfire.davdroid.network.HttpClientBuilder
import at.bitfire.davdroid.servicedetection.DavResourceFinder.Configuration.ServiceInfo
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.util.SensitiveString.Companion.toSensitiveString
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.After
import org.junit.Assert.*
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.net.URI
import java.util.logging.Logger
import javax.inject.Inject
@HiltAndroidTest
class DavResourceFinderTest {
companion object {
@@ -40,41 +47,54 @@ class DavResourceFinderTest {
private const val SUBPATH_ADDRESSBOOK = "/addressbooks/private-contacts"
}
val server = MockWebServer()
@get:Rule
val hiltRule = HiltAndroidRule(this)
lateinit var finder: DavResourceFinder
lateinit var client: HttpClient
lateinit var loginModel: LoginModel
@Inject
lateinit var httpClientBuilder: HttpClientBuilder
@Inject
lateinit var logger: Logger
@Inject
lateinit var resourceFinderFactory: DavResourceFinder.Factory
private lateinit var server: MockWebServer
private lateinit var client: OkHttpClient
private lateinit var finder: DavResourceFinder
@Before
fun initServerAndClient() {
server.dispatcher = TestDispatcher()
server.start()
fun setUp() {
hiltRule.inject()
loginModel = LoginModel()
loginModel.baseURI = URI.create("/")
loginModel.credentials = Credentials("mock", "12345")
server = MockWebServer().apply {
dispatcher = TestDispatcher(logger)
start()
}
finder = DavResourceFinder(InstrumentationRegistry.getInstrumentation().targetContext, loginModel)
client = HttpClient.Builder()
.addAuthentication(null, loginModel.credentials!!)
val credentials = Credentials(username = "mock", password = "12345".toSensitiveString())
client = httpClientBuilder
.authenticate(domain = null, getCredentials = { credentials })
.build()
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
val baseURI = URI.create("/")
finder = resourceFinderFactory.create(baseURI, credentials)
}
@After
fun stopServer() {
fun tearDown() {
server.shutdown()
}
@Test
@SmallTest
fun testRememberIfAddressBookOrHomeset() {
// recognize home set
var info = ServiceInfo()
DavResource(client.okHttpClient, server.url(PATH_CARDDAV + SUBPATH_PRINCIPAL))
.propfind(0, AddressbookHomeSet.NAME) { response, _ ->
finder.scanCardDavResponse(response, info)
DavResource(client, server.url(PATH_CARDDAV + SUBPATH_PRINCIPAL))
.propfind(0, CardDAV.AddressbookHomeSet) { response, _ ->
finder.scanResponse(CardDAV.Addressbook, response, info)
}
assertEquals(0, info.collections.size)
assertEquals(1, info.homeSets.size)
@@ -82,9 +102,9 @@ class DavResourceFinderTest {
// recognize address book
info = ServiceInfo()
DavResource(client.okHttpClient, server.url(PATH_CARDDAV + SUBPATH_ADDRESSBOOK))
.propfind(0, ResourceType.NAME) { response, _ ->
finder.scanCardDavResponse(response, info)
DavResource(client, server.url(PATH_CARDDAV + SUBPATH_ADDRESSBOOK))
.propfind(0, WebDAV.ResourceType) { response, _ ->
finder.scanResponse(CardDAV.Addressbook, response, info)
}
assertEquals(1, info.collections.size)
assertEquals(server.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"), info.collections.keys.first())
@@ -137,7 +157,9 @@ class DavResourceFinderTest {
// mock server
class TestDispatcher: Dispatcher() {
class TestDispatcher(
private val logger: Logger
): Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
if (!checkAuth(request)) {
@@ -186,7 +208,7 @@ class DavResourceFinderTest {
else -> props = null
}
Logger.log.info("Sending props: $props")
logger.info("Sending props: $props")
return MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav' xmlns:CAL='urn:ietf:params:xml:ns:caldav'>" +

View File

@@ -0,0 +1,473 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.servicedetection
import android.security.NetworkSecurityPolicy
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.HomeSet
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.network.HttpClientBuilder
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit4.MockKRule
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.test.runTest
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.logging.Logger
import javax.inject.Inject
@HiltAndroidTest
class HomeSetRefresherTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockKRule = MockKRule(this)
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var httpClientBuilder: HttpClientBuilder
@Inject
lateinit var logger: Logger
@Inject
lateinit var homeSetRefresherFactory: HomeSetRefresher.Factory
@BindValue
@MockK(relaxed = true)
lateinit var settings: SettingsManager
private lateinit var client: OkHttpClient
private lateinit var mockServer: MockWebServer
private lateinit var service: Service
@Before
fun setUp() {
hiltRule.inject()
// Start mock web server
mockServer = MockWebServer().apply {
dispatcher = TestDispatcher(logger)
start()
}
// build HTTP client
client = httpClientBuilder.build()
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
// insert test service
val serviceId = db.serviceDao().insertOrReplace(
Service(id = 0, accountName = "test", type = Service.TYPE_CARDDAV, principal = null)
)
service = db.serviceDao().get(serviceId)!!
}
@After
fun tearDown() {
mockServer.shutdown()
}
// refreshHomesetsAndTheirCollections
@Test
fun refreshHomesetsAndTheirCollections_addsNewCollection() = runTest {
// save homeset in DB
val homesetId = db.homeSetDao().insert(
HomeSet(id = 0, service.id, true, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL"))
)
// Refresh
homeSetRefresherFactory.create(service, client)
.refreshHomesetsAndTheirCollections()
// Check the collection defined in homeset is now in the database
assertEquals(
Collection(
1,
service.id,
homesetId,
1, // will have gotten an owner too
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"),
displayName = "My Contacts",
description = "My Contacts Description"
),
db.collectionDao().getByService(service.id).first()
)
}
@Test
fun refreshHomesetsAndTheirCollections_updatesExistingCollection() {
// save "old" collection in DB
val collectionId = db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
null,
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"),
displayName = "My Contacts",
description = "My Contacts Description"
)
)
// Refresh
homeSetRefresherFactory.create(service, client).refreshHomesetsAndTheirCollections()
// Check the collection got updated
assertEquals(
Collection(
collectionId,
service.id,
null,
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"),
displayName = "My Contacts",
description = "My Contacts Description"
),
db.collectionDao().get(collectionId)
)
}
@Test
fun refreshHomesetsAndTheirCollections_preservesCollectionFlags() {
// save "old" collection in DB - with set flags
val collectionId = db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
null,
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"),
displayName = "My Contacts",
description = "My Contacts Description",
forceReadOnly = true,
sync = true
)
)
// Refresh
homeSetRefresherFactory.create(service, client).refreshHomesetsAndTheirCollections()
// Check the collection got updated
assertEquals(
Collection(
collectionId,
service.id,
null,
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"),
displayName = "My Contacts",
description = "My Contacts Description",
forceReadOnly = true,
sync = true
),
db.collectionDao().get(collectionId)
)
}
@Test
fun refreshHomesetsAndTheirCollections_marksRemovedCollectionsAsHomeless() {
// save homeset in DB - which is empty (zero address books) on the serverside
val homesetId = db.homeSetDao().insert(
HomeSet(id = 0, service.id, true, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET_EMPTY"))
)
// place collection in DB - as part of the homeset
val collectionId = db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
homesetId,
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/")
)
)
// Refresh - should mark collection as homeless, because serverside homeset is empty.
homeSetRefresherFactory.create(service, client).refreshHomesetsAndTheirCollections()
// Check the collection, is now marked as homeless
assertEquals(null, db.collectionDao().get(collectionId)!!.homeSetId)
}
@Test
fun refreshHomesetsAndTheirCollections_addsOwnerUrls() {
// save a homeset in DB
val homesetId = db.homeSetDao().insert(
HomeSet(id = 0, service.id, true, mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL"))
)
// place collection in DB - as part of the homeset
val collectionId = db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
homesetId, // part of above home set
null,
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/")
)
)
// Refresh - homesets and their collections
assertEquals(0, db.principalDao().getByService(service.id).size)
homeSetRefresherFactory.create(service, client).refreshHomesetsAndTheirCollections()
// Check principal saved and the collection was updated with its reference
val principals = db.principalDao().getByService(service.id)
assertEquals(1, principals.size)
assertEquals(mockServer.url("$PATH_CARDDAV$SUBPATH_PRINCIPAL"), principals[0].url)
assertEquals(null, principals[0].displayName)
assertEquals(
principals[0].id,
db.collectionDao().get(collectionId)!!.ownerId
)
}
// other
@Test
fun shouldPreselect_none() {
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_NONE
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns ""
val collection = Collection(
0,
service.id,
0,
type = Collection.TYPE_ADDRESSBOOK,
url = mockServer.url("/addressbook-homeset/addressbook/")
)
val homesets = listOf(
HomeSet(
id = 0,
serviceId = service.id,
personal = true,
url = mockServer.url("/addressbook-homeset/")
)
)
val refresher = homeSetRefresherFactory.create(service, client)
assertFalse(refresher.shouldPreselect(collection, homesets))
}
@Test
fun shouldPreselect_all() {
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_ALL
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns ""
val collection = Collection(
0,
service.id,
0,
type = Collection.TYPE_ADDRESSBOOK,
url = mockServer.url("/addressbook-homeset/addressbook/")
)
val homesets = listOf(
HomeSet(
id = 0,
serviceId = service.id,
personal = false,
url = mockServer.url("/addressbook-homeset/")
)
)
val refresher = homeSetRefresherFactory.create(service, client)
assertTrue(refresher.shouldPreselect(collection, homesets))
}
@Test
fun shouldPreselect_all_blacklisted() {
val url = mockServer.url("/addressbook-homeset/addressbook/")
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_ALL
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns url.toString()
val collection = Collection(
id = 0,
serviceId = service.id,
homeSetId = 0,
type = Collection.TYPE_ADDRESSBOOK,
url = url
)
val homesets = listOf(
HomeSet(
id = 0,
serviceId = service.id,
personal = false,
url = mockServer.url("/addressbook-homeset/")
)
)
val refresher = homeSetRefresherFactory.create(service, client)
assertFalse(refresher.shouldPreselect(collection, homesets))
}
@Test
fun shouldPreselect_personal_notPersonal() {
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_PERSONAL
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns ""
val collection = Collection(
id = 0,
serviceId = service.id,
homeSetId = 0,
type = Collection.TYPE_ADDRESSBOOK,
url = mockServer.url("/addressbook-homeset/addressbook/")
)
val homesets = listOf(
HomeSet(
id = 0,
serviceId = service.id,
personal = false,
url = mockServer.url("/addressbook-homeset/")
)
)
val refresher = homeSetRefresherFactory.create(service, client)
assertFalse(refresher.shouldPreselect(collection, homesets))
}
@Test
fun shouldPreselect_personal_isPersonal() {
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_PERSONAL
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns ""
val collection = Collection(
0,
service.id,
0,
type = Collection.TYPE_ADDRESSBOOK,
url = mockServer.url("/addressbook-homeset/addressbook/")
)
val homesets = listOf(
HomeSet(
id = 0,
serviceId = service.id,
personal = true,
url = mockServer.url("/addressbook-homeset/")
)
)
val refresher = homeSetRefresherFactory.create(service, client)
assertTrue(refresher.shouldPreselect(collection, homesets))
}
@Test
fun shouldPreselect_personal_isPersonalButBlacklisted() {
val collectionUrl = mockServer.url("/addressbook-homeset/addressbook/")
every { settings.getIntOrNull(Settings.PRESELECT_COLLECTIONS) } returns Settings.PRESELECT_COLLECTIONS_PERSONAL
every { settings.getString(Settings.PRESELECT_COLLECTIONS_EXCLUDED) } returns collectionUrl.toString()
val collection = Collection(
id = 0,
serviceId = service.id,
homeSetId = 0,
type = Collection.TYPE_ADDRESSBOOK,
url = collectionUrl
)
val homesets = listOf(
HomeSet(
id = 0,
serviceId = service.id,
personal = true,
url = mockServer.url("/addressbook-homeset/")
)
)
val refresher = homeSetRefresherFactory.create(service, client)
assertFalse(refresher.shouldPreselect(collection, homesets))
}
companion object {
private const val PATH_CARDDAV = "/carddav"
private const val SUBPATH_PRINCIPAL = "/principal"
private const val SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL = "/addressbooks-homeset"
private const val SUBPATH_ADDRESSBOOK_HOMESET_EMPTY = "/addressbooks-homeset-empty"
private const val SUBPATH_ADDRESSBOOK = "/addressbooks/my-contacts"
}
class TestDispatcher(
private val logger: Logger
) : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val path = request.path!!.trimEnd('/')
if (request.method.equals("PROPFIND", true)) {
val properties = when (path) {
PATH_CARDDAV + SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL ->
"<resourcetype>" +
" <collection/>" +
" <CARD:addressbook/>" +
"</resourcetype>" +
"<displayname>My Contacts</displayname>" +
"<CARD:addressbook-description>My Contacts Description</CARD:addressbook-description>" +
"<owner>" +
" <href>${PATH_CARDDAV + SUBPATH_PRINCIPAL}</href>" +
"</owner>"
SUBPATH_ADDRESSBOOK_HOMESET_EMPTY -> ""
else -> ""
}
logger.info("Queried: $path")
return MockResponse()
.setResponseCode(207)
.setBody(
"<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav' xmlns:CAL='urn:ietf:params:xml:ns:caldav'>" +
"<response>" +
" <href>${PATH_CARDDAV + SUBPATH_ADDRESSBOOK}</href>" +
" <propstat><prop>" +
properties +
" </prop></propstat>" +
" <status>HTTP/1.1 200 OK</status>" +
"</response>" +
"</multistatus>"
)
}
return MockResponse().setResponseCode(404)
}
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.servicedetection
import android.security.NetworkSecurityPolicy
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Principal
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.network.HttpClientBuilder
import at.bitfire.davdroid.settings.SettingsManager
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.impl.annotations.MockK
import io.mockk.junit4.MockKRule
import junit.framework.TestCase.assertEquals
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.logging.Logger
import javax.inject.Inject
@HiltAndroidTest
class PrincipalsRefresherTest {
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var httpClientBuilder: HttpClientBuilder
@Inject
lateinit var logger: Logger
@Inject
lateinit var principalsRefresher: PrincipalsRefresher.Factory
@BindValue
@MockK(relaxed = true)
lateinit var settings: SettingsManager
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockKRule = MockKRule(this)
private lateinit var client: OkHttpClient
private lateinit var mockServer: MockWebServer
private lateinit var service: Service
@Before
fun setUp() {
hiltRule.inject()
// Start mock web server
mockServer = MockWebServer().apply {
dispatcher = TestDispatcher(logger)
start()
}
// build HTTP client
client = httpClientBuilder.build()
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
// insert test service
val serviceId = db.serviceDao().insertOrReplace(
Service(id = 0, accountName = "test", type = Service.TYPE_CARDDAV, principal = null)
)
service = db.serviceDao().get(serviceId)!!
}
@After
fun tearDown() {
mockServer.shutdown()
}
@Test
fun refreshPrincipals_inaccessiblePrincipal() {
// place principal without display name in db
val principalId = db.principalDao().insert(
Principal(
0,
service.id,
mockServer.url("$PATH_CARDDAV$SUBPATH_PRINCIPAL_INACCESSIBLE"), // no trailing slash
null // no display name for now
)
)
// add an associated collection - as the principal is rightfully removed otherwise
db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
null,
principalId, // create association with principal
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"), // with trailing slash
)
)
// Refresh principals
principalsRefresher.create(service, client).refreshPrincipals()
// Check principal was not updated
val principals = db.principalDao().getByService(service.id)
assertEquals(1, principals.size)
assertEquals(mockServer.url("$PATH_CARDDAV$SUBPATH_PRINCIPAL_INACCESSIBLE"), principals[0].url)
assertEquals(null, principals[0].displayName)
}
@Test
fun refreshPrincipals_updatesPrincipal() {
// place principal without display name in db
val principalId = db.principalDao().insert(
Principal(
0,
service.id,
mockServer.url("$PATH_CARDDAV$SUBPATH_PRINCIPAL"), // no trailing slash
null // no display name for now
)
)
// add an associated collection - as the principal is rightfully removed otherwise
db.collectionDao().insertOrUpdateByUrl(
Collection(
0,
service.id,
null,
principalId, // create association with principal
Collection.TYPE_ADDRESSBOOK,
mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK/"), // with trailing slash
)
)
// Refresh principals
principalsRefresher.create(service, client).refreshPrincipals()
// Check principal now got a display name
val principals = db.principalDao().getByService(service.id)
assertEquals(1, principals.size)
assertEquals(mockServer.url("$PATH_CARDDAV$SUBPATH_PRINCIPAL"), principals[0].url)
assertEquals("Mr. Wobbles", principals[0].displayName)
}
@Test
fun refreshPrincipals_deletesPrincipalsWithoutCollections() {
// place principal without collections in DB
db.principalDao().insert(
Principal(
0,
service.id,
mockServer.url("$PATH_CARDDAV$SUBPATH_PRINCIPAL_WITHOUT_COLLECTIONS/")
)
)
// Refresh principals - detecting it does not own collections
principalsRefresher.create(service, client).refreshPrincipals()
// Check principal was deleted
val principals = db.principalDao().getByService(service.id)
assertEquals(0, principals.size)
}
companion object {
private const val PATH_CARDDAV = "/carddav"
private const val SUBPATH_PRINCIPAL = "/principal"
private const val SUBPATH_PRINCIPAL_INACCESSIBLE = "/inaccessible-principal"
private const val SUBPATH_PRINCIPAL_WITHOUT_COLLECTIONS = "/principal2"
private const val SUBPATH_GROUPPRINCIPAL_0 = "/groups/0"
private const val SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL = "/addressbooks-homeset"
private const val SUBPATH_ADDRESSBOOK_HOMESET_EMPTY = "/addressbooks-homeset-empty"
private const val SUBPATH_ADDRESSBOOK = "/addressbooks/my-contacts"
}
class TestDispatcher(
private val logger: Logger
) : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val path = request.path!!.trimEnd('/')
if (request.method.equals("PROPFIND", true)) {
val properties = when (path) {
PATH_CARDDAV + SUBPATH_PRINCIPAL ->
"<resourcetype><principal/></resourcetype>" +
"<displayname>Mr. Wobbles</displayname>" + "<CARD:addressbook-home-set>" + " <href>${PATH_CARDDAV}${SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL}</href>" + "</CARD:addressbook-home-set>" + "<group-membership>" + " <href>${PATH_CARDDAV}${SUBPATH_GROUPPRINCIPAL_0}</href>" +
"</group-membership>"
PATH_CARDDAV + SUBPATH_PRINCIPAL_WITHOUT_COLLECTIONS ->
"<CARD:addressbook-home-set>" +
" <href>${PATH_CARDDAV}${SUBPATH_ADDRESSBOOK_HOMESET_EMPTY}</href>" +
"</CARD:addressbook-home-set>" +
"<displayname>Mr. Wobbles Jr.</displayname>"
SUBPATH_ADDRESSBOOK_HOMESET_EMPTY -> ""
else -> ""
}
logger.info("Queried: $path")
return MockResponse()
.setResponseCode(207)
.setBody(
"<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav' xmlns:CAL='urn:ietf:params:xml:ns:caldav'>" +
"<response>" +
" <href>$path</href>" +
" <propstat><prop>" +
properties +
" </prop></propstat>" +
"</response>" +
"</multistatus>"
)
}
return MockResponse().setResponseCode(404)
}
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.servicedetection
import android.security.NetworkSecurityPolicy
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.network.HttpClientBuilder
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.logging.Logger
import javax.inject.Inject
@HiltAndroidTest
class ServiceRefresherTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var httpClientBuilder: HttpClientBuilder
@Inject
lateinit var logger: Logger
@Inject
lateinit var serviceRefresherFactory: ServiceRefresher.Factory
private lateinit var client: OkHttpClient
private lateinit var mockServer: MockWebServer
private lateinit var service: Service
@Before
fun setUp() {
hiltRule.inject()
// Start mock web server
mockServer = MockWebServer().apply {
dispatcher = TestDispatcher(logger)
start()
}
// build HTTP client
client = httpClientBuilder.build()
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
// insert test service
val serviceId = db.serviceDao().insertOrReplace(
Service(id = 0, accountName = "test", type = Service.TYPE_CARDDAV, principal = null)
)
service = db.serviceDao().get(serviceId)!!
}
@After
fun tearDown() {
mockServer.shutdown()
}
@Test
fun testDiscoverHomesets() {
val baseUrl = mockServer.url(PATH_CARDDAV + SUBPATH_PRINCIPAL)
// Query home sets
serviceRefresherFactory.create(service, client)
.discoverHomesets(baseUrl)
// Check home set has been saved correctly to database
val savedHomesets = db.homeSetDao().getByService(service.id)
assertEquals(2, savedHomesets.size)
// Home set from current-user-principal
val personalHomeset = savedHomesets[1]
assertEquals(mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL/"), personalHomeset.url)
assertEquals(service.id, personalHomeset.serviceId)
// personal should be true for homesets detected at first query of current-user-principal (Even if they occur in a group principal as well!!!)
assertEquals(true, personalHomeset.personal)
// Home set found in a group principal
val groupHomeset = savedHomesets[0]
assertEquals(mockServer.url("$PATH_CARDDAV$SUBPATH_ADDRESSBOOK_HOMESET_NON_PERSONAL/"), groupHomeset.url)
assertEquals(service.id, groupHomeset.serviceId)
// personal should be false for homesets not detected at the first query of current-user-principal (IE. in groups)
assertEquals(false, groupHomeset.personal)
}
companion object {
private const val PATH_CARDDAV = "/carddav"
private const val SUBPATH_PRINCIPAL = "/principal"
private const val SUBPATH_GROUPPRINCIPAL_0 = "/groups/0"
private const val SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL = "/addressbooks-homeset"
private const val SUBPATH_ADDRESSBOOK_HOMESET_NON_PERSONAL = "/addressbooks-homeset-non-personal"
}
class TestDispatcher(
private val logger: Logger
) : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val path = request.path!!.trimEnd('/')
logger.info("Query: ${request.method} on $path ")
if (request.method.equals("PROPFIND", true)) {
val properties = when (path) {
PATH_CARDDAV + SUBPATH_PRINCIPAL ->
"<resourcetype><principal/></resourcetype>" +
"<displayname>Mr. Wobbles</displayname>" +
"<CARD:addressbook-home-set>" +
" <href>${PATH_CARDDAV}${SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL}</href>" +
"</CARD:addressbook-home-set>" +
"<group-membership>" +
" <href>${PATH_CARDDAV}${SUBPATH_GROUPPRINCIPAL_0}</href>" +
"</group-membership>"
PATH_CARDDAV + SUBPATH_GROUPPRINCIPAL_0 ->
"<resourcetype><principal/></resourcetype>" +
"<displayname>All address books</displayname>" +
"<CARD:addressbook-home-set>" +
" <href>${PATH_CARDDAV}${SUBPATH_ADDRESSBOOK_HOMESET_PERSONAL}</href>" +
" <href>${PATH_CARDDAV}${SUBPATH_ADDRESSBOOK_HOMESET_NON_PERSONAL}</href>" +
"</CARD:addressbook-home-set>"
else -> ""
}
return MockResponse()
.setResponseCode(207)
.setBody(
"<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav' xmlns:CAL='urn:ietf:params:xml:ns:caldav'>" +
"<response>" +
" <href>$path</href>" +
" <propstat><prop>" +
properties +
" </prop></propstat>" +
"</response>" +
"</multistatus>"
)
}
return MockResponse().setResponseCode(404)
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.settings
import android.accounts.AccountManager
import android.content.Context
import at.bitfire.davdroid.TestUtils
import at.bitfire.davdroid.sync.account.TestAccount
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class AccountSettingsTest {
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var accountSettingsFactory: AccountSettings.Factory
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Before
fun setUp() {
hiltRule.inject()
TestUtils.setUpWorkManager(context)
}
@Test(expected = IllegalArgumentException::class)
fun testUpdate_MissingMigrations() {
TestAccount.provide(version = 1) { account ->
// will run AccountSettings.update
accountSettingsFactory.create(account, abortOnMissingMigration = true)
}
}
@Test
fun testUpdate_RunAllMigrations() {
TestAccount.provide(version = 6) { account ->
// will run AccountSettings.update
accountSettingsFactory.create(account, abortOnMissingMigration = true)
val accountManager = AccountManager.get(context)
val version = accountManager.getUserData(account, AccountSettings.KEY_SETTINGS_VERSION).toInt()
assertEquals(AccountSettings.CURRENT_VERSION, version)
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.settings
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toSet
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class SettingsManagerTest {
companion object {
/** Use this setting to test SettingsManager methods. Will be removed after every test run. */
const val SETTING_TEST = "test"
}
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Inject lateinit var settingsManager: SettingsManager
@Before
fun inject() {
hiltRule.inject()
}
@After
fun removeTestSetting() {
settingsManager.remove(SETTING_TEST)
}
@Test
fun test_containsKey_NotExisting() {
assertFalse(settingsManager.containsKey("notExisting"))
}
@Test
fun test_containsKey_Existing() {
// provided by DefaultsProvider
assertEquals(Settings.PROXY_TYPE_SYSTEM, settingsManager.getInt(Settings.PROXY_TYPE))
}
@Test
fun test_observerFlow_initialValue() = runTest {
var counter = 0
val live = settingsManager.observerFlow {
if (counter++ == 0)
23
else
throw AssertionError("A second value was requested")
}
assertEquals(23, live.first())
}
@Test
fun test_observerFlow_updatedValue() = runTest {
var counter = 0
val live = settingsManager.observerFlow {
when (counter++) {
0 -> {
// update some setting so that we will be called a second time
settingsManager.putBoolean(SETTING_TEST, true)
// and emit initial value
23
}
1 -> 42 // updated value
else -> throw AssertionError()
}
}
val result = live.take(2).toSet()
assertEquals(setOf(23, 42), result)
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.settings.migration
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import androidx.test.rule.GrantPermissionRule
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.davdroid.sync.account.setAndVerifyUserData
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class AccountSettingsMigration17Test {
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var migration: AccountSettingsMigration17
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val permissionRule = GrantPermissionRule.grant(android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS)
@Before
fun setUp() {
hiltRule.inject()
}
@Test
fun testMigrate_OldAddressBook_CollectionInDB() {
val localAddressBookUserDataUrl = "url"
TestAccount.provide(version = 16) { account ->
val accountManager = AccountManager.get(context)
val addressBookAccountType = context.getString(R.string.account_type_address_book)
var addressBookAccount = Account("Address Book", addressBookAccountType)
assertTrue(accountManager.addAccountExplicitly(addressBookAccount, null, null))
try {
// address book has account + URL
val url = "https://example.com/address-book"
accountManager.setAndVerifyUserData(addressBookAccount, "real_account_name", account.name)
accountManager.setAndVerifyUserData(addressBookAccount, localAddressBookUserDataUrl, url)
// and is known in database
db.serviceDao().insertOrReplace(
Service(
id = 1, accountName = account.name, type = Service.TYPE_CARDDAV, principal = null
)
)
db.collectionDao().insert(
Collection(
id = 100,
serviceId = 1,
url = url.toHttpUrl(),
type = Collection.TYPE_ADDRESSBOOK,
displayName = "Some Address Book"
)
)
// run migration
migration.migrate(account)
// migration renames address book, update account
addressBookAccount = accountManager.getAccountsByType(addressBookAccountType).filter {
accountManager.getUserData(it, localAddressBookUserDataUrl) == url
}.first()
assertEquals("Some Address Book (${account.name}) #100", addressBookAccount.name)
// ID is now assigned
assertEquals(100L, accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_COLLECTION_ID)?.toLong())
} finally {
accountManager.removeAccountExplicitly(addressBookAccount)
}
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.settings.migration
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.resource.LocalAddressBook
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.every
import io.mockk.junit4.MockKRule
import io.mockk.mockkObject
import io.mockk.verify
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class AccountSettingsMigration18Test {
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var migration: AccountSettingsMigration18
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockkRule = MockKRule(this)
@Before
fun setUp() {
hiltRule.inject()
}
@Test
fun testMigrate_AddressBook_InvalidCollection() {
val addressBookAccountType = context.getString(R.string.account_type_address_book)
var addressBookAccount = Account("Address Book", addressBookAccountType)
val accountManager = AccountManager.get(context)
mockkObject(accountManager)
every { accountManager.getAccountsByType(addressBookAccountType) } returns arrayOf(addressBookAccount)
every { accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_COLLECTION_ID) } returns "123"
val account = Account("test", "test")
migration.migrate(account)
verify(exactly = 0) {
accountManager.setUserData(addressBookAccount, any(), any())
}
}
@Test
fun testMigrate_AddressBook_NoCollection() {
val addressBookAccountType = context.getString(R.string.account_type_address_book)
var addressBookAccount = Account("Address Book", addressBookAccountType)
val accountManager = AccountManager.get(context)
mockkObject(accountManager)
every { accountManager.getAccountsByType(addressBookAccountType) } returns arrayOf(addressBookAccount)
every { accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_COLLECTION_ID) } returns "123"
val account = Account("test", "test")
migration.migrate(account)
verify(exactly = 0) {
accountManager.setUserData(addressBookAccount, any(), any())
}
}
@Test
fun testMigrate_AddressBook_ValidCollection() {
val account = Account("test", "test")
db.serviceDao().insertOrReplace(Service(
id = 10,
accountName = account.name,
type = Service.TYPE_CARDDAV,
principal = null
))
db.collectionDao().insertOrUpdateByUrl(Collection(
id = 100,
serviceId = 10,
url = "http://example.com".toHttpUrl(),
type = Collection.TYPE_ADDRESSBOOK
))
val addressBookAccountType = context.getString(R.string.account_type_address_book)
var addressBookAccount = Account("Address Book", addressBookAccountType)
val accountManager = AccountManager.get(context)
mockkObject(accountManager)
every { accountManager.getAccountsByType(addressBookAccountType) } returns arrayOf(addressBookAccount)
every { accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_COLLECTION_ID) } returns "100"
migration.migrate(account)
verify {
accountManager.setUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_NAME, account.name)
accountManager.setUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_TYPE, account.type)
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.settings.migration
import android.accounts.Account
import android.content.Context
import android.util.Log
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import androidx.work.WorkManager
import androidx.work.testing.WorkManagerTestInitHelper
import at.bitfire.davdroid.sync.AutomaticSyncManager
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit4.MockKRule
import io.mockk.mockkObject
import io.mockk.verify
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class AccountSettingsMigration19Test {
@Inject @ApplicationContext
lateinit var context: Context
@BindValue
@RelaxedMockK
lateinit var automaticSyncManager: AutomaticSyncManager
@Inject
lateinit var migration: AccountSettingsMigration19
@Inject
lateinit var workerFactory: HiltWorkerFactory
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockkRule = MockKRule(this)
@Before
fun setUp() {
hiltRule.inject()
// Initialize WorkManager for instrumentation tests.
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setWorkerFactory(workerFactory)
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
}
@Test
fun testMigrate_CancelsOldWorkersAndUpdatesAutomaticSync() {
val workManager = WorkManager.getInstance(context)
mockkObject(workManager)
val account = Account("Some", "Test")
migration.migrate(account)
verify {
workManager.cancelUniqueWork("periodic-sync at.bitfire.davdroid.addressbooks Test/Some")
workManager.cancelUniqueWork("periodic-sync com.android.calendar Test/Some")
workManager.cancelUniqueWork("periodic-sync at.techbee.jtx.provider Test/Some")
workManager.cancelUniqueWork("periodic-sync org.dmfs.tasks Test/Some")
workManager.cancelUniqueWork("periodic-sync org.tasks.opentasks Test/Some")
automaticSyncManager.updateAutomaticSync(account)
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.settings.migration
import android.Manifest
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import android.provider.CalendarContract
import android.provider.CalendarContract.Calendars
import androidx.core.content.contentValuesOf
import androidx.core.database.getLongOrNull
import androidx.test.rule.GrantPermissionRule
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.resource.LocalCalendarStore
import at.bitfire.davdroid.resource.LocalTestAddressBookProvider
import at.bitfire.davdroid.sync.account.setAndVerifyUserData
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
import at.bitfire.vcard4android.GroupMethod
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.junit4.MockKRule
import io.mockk.mockk
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class AccountSettingsMigration20Test {
@Inject
lateinit var calendarStore: LocalCalendarStore
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var db: AppDatabase
@Inject
lateinit var migration: AccountSettingsMigration20
@Inject
lateinit var localTestAddressBookProvider: LocalTestAddressBookProvider
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val mockkRule = MockKRule(this)
@get:Rule
val permissionsRule = GrantPermissionRule.grant(
Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR
)
val accountManager by lazy { AccountManager.get(context) }
@Before
fun setUp() {
hiltRule.inject()
}
@Test
fun testMigrateAddressBooks_UrlMatchesCollection() {
// set up legacy address-book with URL, but without collection ID
val account = Account("test", "test")
val url = "https://example.com/"
db.serviceDao().insertOrReplace(Service(id = 1, accountName = account.name, type = Service.TYPE_CARDDAV, principal = null))
val collectionId = db.collectionDao().insert(Collection(
serviceId = 1,
type = Collection.Companion.TYPE_ADDRESSBOOK,
url = url.toHttpUrl()
))
localTestAddressBookProvider.provide(account, mockk(relaxed = true), GroupMethod.GROUP_VCARDS) { addressBook ->
accountManager.setAndVerifyUserData(addressBook.addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_NAME, account.name)
accountManager.setAndVerifyUserData(addressBook.addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_TYPE, account.type)
accountManager.setAndVerifyUserData(addressBook.addressBookAccount, AccountSettingsMigration20.ADDRESS_BOOK_USER_DATA_URL, url)
accountManager.setAndVerifyUserData(addressBook.addressBookAccount, LocalAddressBook.USER_DATA_COLLECTION_ID, null)
migration.migrateAddressBooks(account, cardDavServiceId = 1)
assertEquals(
collectionId,
accountManager.getUserData(addressBook.addressBookAccount, LocalAddressBook.USER_DATA_COLLECTION_ID).toLongOrNull()
)
}
}
@Test
fun testMigrateCalendars_UrlMatchesCollection() {
// set up legacy calendar with URL, but without collection ID
val account = Account("test", CalendarContract.ACCOUNT_TYPE_LOCAL)
val url = "https://example.com/"
db.serviceDao().insertOrReplace(Service(id = 1, accountName = account.name, type = Service.TYPE_CALDAV, principal = null))
val collectionId = db.collectionDao().insert(
Collection(
serviceId = 1,
type = Collection.Companion.TYPE_CALENDAR,
url = url.toHttpUrl()
)
)
context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!.use { provider ->
val uri = provider.insert(
Calendars.CONTENT_URI.asSyncAdapter(account),
contentValuesOf(
Calendars.ACCOUNT_NAME to account.name,
Calendars.ACCOUNT_TYPE to account.type,
Calendars.CALENDAR_DISPLAY_NAME to "Test",
Calendars.NAME to url,
Calendars.SYNC_EVENTS to 1
)
)!!.asSyncAdapter(account)
try {
migration.migrateCalendars(account, 1)
provider.query(uri, arrayOf(Calendars._SYNC_ID), null, null, null)!!.use { cursor ->
cursor.moveToNext()
assertEquals(collectionId, cursor.getLongOrNull(0))
}
} finally {
provider.delete(uri, null, null)
}
}
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.settings.migration
import android.accounts.Account
import android.content.ContentResolver
import android.content.Context
import android.content.SyncRequest
import android.os.Bundle
import android.provider.CalendarContract
import androidx.test.filters.SdkSuppress
import at.bitfire.davdroid.sync.account.TestAccount
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.After
import org.junit.AfterClass
import org.junit.Assert.assertFalse
import org.junit.Assume
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import java.util.logging.Logger
import javax.inject.Inject
@HiltAndroidTest
class AccountSettingsMigration21Test {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var migration: AccountSettingsMigration21
@Inject
@ApplicationContext
lateinit var context: Context
@Inject
lateinit var logger: Logger
lateinit var account: Account
val authority = CalendarContract.AUTHORITY
private val inPendingState = MutableStateFlow(false)
private var statusChangeListener: Any? = null
@Before
fun setUp() {
hiltRule.inject()
account = TestAccount.create()
// Enable sync globally and for the test account
ContentResolver.setIsSyncable(account, authority, 1)
// Start hot flow
registerSyncStateObserver()
}
@After
fun tearDown() {
unregisterSyncStateObserver()
TestAccount.remove(account)
}
@SdkSuppress(minSdkVersion = 34)
@Test
fun testCancelsSyncAndClearsPendingState() = runBlocking {
// Move into forever pending state
ContentResolver.requestSync(syncRequest())
// Wait until we are in forever pending state (with timeout)
withTimeout(10_000) {
inPendingState.first { it }
}
// Assume that we are now in the forever pending state (Skips test otherwise)
Assume.assumeTrue(ContentResolver.isSyncPending(account, authority))
// Run the migration which should cancel the forever pending sync for all accounts
migration.migrate(account)
// Wait for the state to change (with timeout)
withTimeout(10_000) {
inPendingState.first { !it }
}
// Check the sync is now not pending anymore
assertFalse(ContentResolver.isSyncPending(account, authority))
}
// helpers
private fun syncRequest() = SyncRequest.Builder()
.setSyncAdapter(account, authority)
.syncOnce()
.setExtras(Bundle()) // needed for Android 9
.setExpedited(true) // sync request will be scheduled at the front of the sync request queue
.setManual(true) // equivalent of setting both SYNC_EXTRAS_IGNORE_SETTINGS and SYNC_EXTRAS_IGNORE_BACKOFF
.build()
private fun registerSyncStateObserver() {
// listener pushes updates immediately when sync status changes
statusChangeListener = ContentResolver.addStatusChangeListener(
ContentResolver.SYNC_OBSERVER_TYPE_PENDING or ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
) {
inPendingState.tryEmit(ContentResolver.isSyncPending(account, authority))
}
// Emit initial state
inPendingState.tryEmit(ContentResolver.isSyncPending(account, authority))
}
private fun unregisterSyncStateObserver() {
statusChangeListener?.let { ContentResolver.removeStatusChangeListener(it) }
}
companion object {
var globalAutoSyncBeforeTest = false
@BeforeClass
@JvmStatic
fun before() {
globalAutoSyncBeforeTest = ContentResolver.getMasterSyncAutomatically()
// We'll request syncs explicitly and with SYNC_EXTRAS_IGNORE_SETTINGS
ContentResolver.setMasterSyncAutomatically(false)
}
@AfterClass
@JvmStatic
fun after() {
ContentResolver.setMasterSyncAutomatically(globalAutoSyncBeforeTest)
}
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.sync
import android.accounts.Account
import android.content.ContentResolver
import android.content.SyncRequest
import android.content.SyncStatusObserver
import android.os.Bundle
import android.provider.CalendarContract
import androidx.test.filters.SdkSuppress
import at.bitfire.davdroid.sync.account.TestAccount
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.After
import org.junit.AfterClass
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import java.util.Collections
import java.util.LinkedList
import java.util.logging.Logger
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltAndroidTest
class AndroidSyncFrameworkTest: SyncStatusObserver {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var logger: Logger
lateinit var account: Account
val authority = CalendarContract.AUTHORITY
private lateinit var stateChangeListener: Any
private val recordedStates = Collections.synchronizedList(LinkedList<State>())
@Before
fun setUp() {
hiltRule.inject()
account = TestAccount.create()
// Enable sync globally and for the test account
ContentResolver.setIsSyncable(account, authority, 1)
// Remember states the sync framework reports as pairs of (sync pending, sync active).
recordedStates.clear()
onStatusChanged(0) // record first entry (pending = false, active = false)
stateChangeListener = ContentResolver.addStatusChangeListener(
ContentResolver.SYNC_OBSERVER_TYPE_PENDING or ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE,
this
)
}
@After
fun tearDown() {
ContentResolver.removeStatusChangeListener(stateChangeListener)
TestAccount.remove(account)
}
/**
* Correct behaviour of the sync framework on Android 13 and below.
* Pending state is correctly reflected
*/
@SdkSuppress(maxSdkVersion = 33)
@Test
fun testVerifySyncAlwaysPending_correctBehaviour_android13() {
verifySyncStates(
listOf(
State(pending = false, active = false), // no sync pending or active
State(pending = true, active = false, optional = true), // sync becomes pending
State(pending = true, active = true), // ... and pending and active at the same time
State(pending = false, active = true), // ... and then only active
State(pending = false, active = false) // sync finished
)
)
}
/* SHOULD BE FIXED WITH https://github.com/bitfireAT/davx5-ose/issues/1748
* Wrong behaviour of the sync framework on Android 14+.
* Pending state stays true forever (after initial run), active state behaves correctly
*/
/*@SdkSuppress(minSdkVersion = 34 /*, maxSdkVersion = 36 */)
@Test
fun testVerifySyncAlwaysPending_wrongBehaviour_android14() {
verifySyncStates(
listOf(
State(pending = false, active = false), // no sync pending or active
State(pending = true, active = false, optional = true), // sync becomes pending
State(pending = true, active = true), // ... and pending and active at the same time
State(pending = true, active = false) // ... and finishes, but stays pending
)
)
}*/
// helpers
private fun syncRequest() = SyncRequest.Builder()
.setSyncAdapter(account, authority)
.syncOnce()
.setExtras(Bundle()) // needed for Android 9
.setExpedited(true) // sync request will be scheduled at the front of the sync request queue
.setManual(true) // equivalent of setting both SYNC_EXTRAS_IGNORE_SETTINGS and SYNC_EXTRAS_IGNORE_BACKOFF
.build()
/**
* Verifies that the given expected states match the recorded states.
*/
private fun verifySyncStates(expectedStates: List<State>) = runBlocking {
// Verify that last state is non-optional.
if (expectedStates.last().optional)
throw IllegalArgumentException("Last expected state must not be optional")
// We use runBlocking for these tests because it uses the default dispatcher
// which does not auto-advance virtual time and we need real system time to
// test the sync framework behavior.
ContentResolver.requestSync(syncRequest())
// Even though the always-pending-bug is present on Android 14+, the sync active
// state behaves correctly, so we can record the state changes as pairs (pending,
// active) and expect a certain sequence of state pairs to verify the presence or
// absence of the bug on different Android versions.
withTimeout(60.seconds) { // Usually takes less than 30 seconds
while (recordedStates.size < expectedStates.size) {
// verify already known states
if (recordedStates.isNotEmpty())
assertStatesEqual(expectedStates, recordedStates, fullMatch = false)
delay(500) // avoid busy-waiting
}
assertStatesEqual(expectedStates, recordedStates, fullMatch = true)
}
}
private fun assertStatesEqual(expectedStates: List<State>, actualStates: List<State>, fullMatch: Boolean) {
assertTrue("Expected states=$expectedStates, actual=$actualStates", statesMatch(expectedStates, actualStates, fullMatch))
}
/**
* Checks whether [actualStates] have matching [expectedStates], under the condition
* that expected states with the [State.optional] flag can be skipped.
*
* Note: When [fullMatch] is not set, this method can return _true_ even if not all expected states are used.
*
* @param expectedStates expected states (can include optional states which don't have to be present in actual states)
* @param actualStates actual states
* @param fullMatch whether all non-optional expected states must be present in actual states
*/
private fun statesMatch(expectedStates: List<State>, actualStates: List<State>, fullMatch: Boolean): Boolean {
// iterate through entries
val expectedIterator = expectedStates.iterator()
for (actual in actualStates) {
if (!expectedIterator.hasNext())
return false
var expected = expectedIterator.next()
// skip optional expected entries if they don't match the actual entry
while (!actual.stateEquals(expected) && expected.optional) {
if (!expectedIterator.hasNext())
return false
expected = expectedIterator.next()
}
// we now have a non-optional expected state and it must match
if (!actual.stateEquals(expected))
return false
}
// full match: all expected states must have been used
if (fullMatch && expectedIterator.hasNext())
return false
return true
}
// SyncStatusObserver implementation and data class
override fun onStatusChanged(which: Int) {
val state = State(
pending = ContentResolver.isSyncPending(account, authority),
active = ContentResolver.isSyncActive(account, authority)
)
synchronized(recordedStates) {
if (recordedStates.lastOrNull() != state) {
logger.info("$account syncState = $state")
recordedStates += state
}
}
}
data class State(
val pending: Boolean,
val active: Boolean,
val optional: Boolean = false
) {
fun stateEquals(other: State) =
pending == other.pending && active == other.active
}
companion object {
var globalAutoSyncBeforeTest = false
@BeforeClass
@JvmStatic
fun before() {
globalAutoSyncBeforeTest = ContentResolver.getMasterSyncAutomatically()
// We'll request syncs explicitly and with SYNC_EXTRAS_IGNORE_SETTINGS
ContentResolver.setMasterSyncAutomatically(false)
}
@AfterClass
@JvmStatic
fun after() {
ContentResolver.setMasterSyncAutomatically(globalAutoSyncBeforeTest)
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.sync
import android.Manifest
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.Context
import android.content.Entity
import android.provider.CalendarContract
import android.provider.CalendarContract.Calendars
import android.provider.CalendarContract.Events
import androidx.core.content.contentValuesOf
import androidx.test.rule.GrantPermissionRule
import at.bitfire.davdroid.resource.LocalCalendar
import at.bitfire.davdroid.resource.LocalEvent
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.storage.calendar.AndroidCalendar
import at.bitfire.synctools.storage.calendar.AndroidCalendarProvider
import at.bitfire.synctools.storage.calendar.EventAndExceptions
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.mockk.mockk
import okio.Buffer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
@HiltAndroidTest
class CalendarSyncManagerTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val permissionsRule = GrantPermissionRule.grant(
Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR
)
@Inject @ApplicationContext
lateinit var context: Context
@Inject
lateinit var localCalendarFactory: LocalCalendar.Factory
@Inject
lateinit var syncManagerFactory: CalendarSyncManager.Factory
lateinit var account: Account
lateinit var providerClient: ContentProviderClient
lateinit var androidCalendar: AndroidCalendar
lateinit var localCalendar: LocalCalendar
@Before
fun setUp() {
hiltRule.inject()
account = TestAccount.create()
providerClient = context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)!!
// create LocalCalendar
val androidCalendarProvider = AndroidCalendarProvider(account, providerClient)
androidCalendar = androidCalendarProvider.createAndGetCalendar(contentValuesOf(
Calendars.NAME to "Sample Calendar"
))
localCalendar = localCalendarFactory.create(androidCalendar)
}
@After
fun tearDown() {
localCalendar.androidCalendar.delete()
providerClient.closeCompat()
TestAccount.remove(account)
}
@Test
fun test_generateUpload_existingUid() {
val result = syncManager().generateUpload(LocalEvent(
localCalendar.recurringCalendar,
EventAndExceptions(
main = Entity(contentValuesOf(
Events._ID to 1,
Events.CALENDAR_ID to androidCalendar.id,
Events.DTSTART to System.currentTimeMillis(),
Events.UID_2445 to "existing-uid"
)),
exceptions = emptyList()
)
))
assertEquals("existing-uid.ics", result.suggestedFileName)
val iCal = Buffer().also {
result.requestBody.writeTo(it)
}.readString(Charsets.UTF_8)
assertTrue(iCal.contains("UID:existing-uid\r\n"))
}
@Test
fun generateUpload_noUid() {
val result = syncManager().generateUpload(LocalEvent(
localCalendar.recurringCalendar,
EventAndExceptions(
main = Entity(contentValuesOf(
Events._ID to 2,
Events.CALENDAR_ID to androidCalendar.id,
Events.DTSTART to System.currentTimeMillis()
)),
exceptions = emptyList()
)
))
assertTrue(result.suggestedFileName.matches(UUID_FILENAME_REGEX))
val uuid = result.suggestedFileName.removeSuffix(".ics")
val iCal = Buffer().also {
result.requestBody.writeTo(it)
}.readString(Charsets.UTF_8)
assertTrue(iCal.contains("UID:$uuid\r\n"))
}
// helpers
private fun syncManager() = syncManagerFactory.calendarSyncManager(
account = account,
httpClient = mockk(),
syncResult = mockk(),
localCalendar = mockk(),
collection = mockk(),
resync = mockk()
)
companion object {
val UUID_FILENAME_REGEX = "^[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}\\.ics$".toRegex()
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.sync
import android.accounts.Account
import android.content.AbstractThreadedSyncAdapter
import android.content.ContentProviderClient
import android.content.Context
import android.content.SyncResult
import android.os.Bundle
import android.os.IBinder
import at.bitfire.davdroid.sync.adapter.SyncAdapter
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
class FakeSyncAdapter @Inject constructor(
@ApplicationContext context: Context,
private val logger: Logger
): AbstractThreadedSyncAdapter(context, true), SyncAdapter {
init {
logger.info("FakeSyncAdapter created")
}
override fun onPerformSync(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
logger.log(
Level.INFO,
"onPerformSync(account=$account, extras=$extras, authority=$authority, syncResult=$syncResult)",
extras.keySet().map { key -> "extras[$key] = ${extras[key]}" }
)
// fake 5 sec sync
try {
Thread.sleep(5000)
} catch (_: InterruptedException) {
logger.info("onPerformSync($account) cancelled")
}
logger.info("onPerformSync($account) finished")
}
// SyncAdapter implementation and Hilt module
override fun getBinder(): IBinder = syncAdapterBinder
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.sync
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.Context
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.network.HttpClientBuilder
import at.bitfire.davdroid.repository.DavServiceRepository
import at.bitfire.davdroid.resource.LocalJtxCollection
import at.bitfire.davdroid.resource.LocalJtxCollectionStore
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.davdroid.util.PermissionUtils
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.test.GrantPermissionOrSkipRule
import at.techbee.jtx.JtxContract
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assume.assumeNotNull
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.StringReader
import javax.inject.Inject
/**
* Ensure you have jtxBoard installed on the emulator, before running these tests. Otherwise they
* will be skipped.
*/
@HiltAndroidTest
class JtxSyncManagerTest {
@Inject
@ApplicationContext
lateinit var context: Context
@Inject
lateinit var httpClientBuilder: HttpClientBuilder
@Inject
lateinit var serviceRepository: DavServiceRepository
@Inject
lateinit var localJtxCollectionStore: LocalJtxCollectionStore
@Inject
lateinit var jtxSyncManagerFactory: JtxSyncManager.Factory
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val permissionRule = GrantPermissionOrSkipRule(TaskProvider.PERMISSIONS_JTX.toSet())
lateinit var account: Account
private lateinit var provider: ContentProviderClient
private lateinit var syncManager: JtxSyncManager
private lateinit var localJtxCollection: LocalJtxCollection
@Before
fun setUp() {
hiltRule.inject()
// Check jtxBoard permissions were granted (+jtxBoard is installed); skip test otherwise
assumeTrue(PermissionUtils.havePermissions(context, TaskProvider.PERMISSIONS_JTX))
// Acquire the jtx content provider
val providerOrNull = context.contentResolver.acquireContentProviderClient(JtxContract.AUTHORITY)
assumeNotNull(providerOrNull)
provider = providerOrNull!!
account = TestAccount.create()
// Create dummy dependencies
val service = Service(0, account.name, Service.TYPE_CALDAV, null)
val serviceId = serviceRepository.insertOrReplaceBlocking(service)
val dbCollection = Collection(
0,
serviceId,
type = Collection.TYPE_CALENDAR,
url = "https://example.com".toHttpUrl()
)
localJtxCollection = localJtxCollectionStore.create(provider, dbCollection)!!
syncManager = jtxSyncManagerFactory.jtxSyncManager(
account = account,
httpClient = httpClientBuilder.build(),
syncResult = SyncResult(),
localCollection = localJtxCollection,
collection = dbCollection,
resync = null
)
}
@After
fun tearDown() {
if (this::localJtxCollection.isInitialized)
localJtxCollectionStore.delete(localJtxCollection)
serviceRepository.deleteAllBlocking()
if (this::provider.isInitialized)
provider.closeCompat()
if (this::account.isInitialized)
TestAccount.remove(account)
}
@Test
fun testProcessICalObject_addsVtodo() {
val calendar = "BEGIN:VCALENDAR\n" +
"PRODID:-Vivaldi Calendar V1.0//EN\n" +
"VERSION:2.0\n" +
"BEGIN:VTODO\n" +
"SUMMARY:Test Task (Main VTODO)\n" +
"DTSTAMP;VALUE=DATE-TIME:20250228T032800Z\n" +
"UID:47a23c66-8c1a-4b44-bbe8-ebf33f8cf80f\n" +
"END:VTODO\n" +
"END:VCALENDAR"
// Should create "demo-calendar"
syncManager.processICalObject("demo-calendar", "abc123", StringReader(calendar))
// Verify main VTODO is created
val localJtxIcalObject = localJtxCollection.findByName("demo-calendar")!!
assertEquals("47a23c66-8c1a-4b44-bbe8-ebf33f8cf80f", localJtxIcalObject.uid)
assertEquals("abc123", localJtxIcalObject.eTag)
assertEquals("Test Task (Main VTODO)", localJtxIcalObject.summary)
}
@Test
fun testProcessICalObject_addsRecurringVtodo_withoutDtStart() {
// Valid calendar example (See bitfireAT/davx5-ose#1265)
// Note: We don't support starting a recurrence from DUE (RFC 5545 leaves it open to interpretation)
val calendar = "BEGIN:VCALENDAR\n" +
"PRODID:-Vivaldi Calendar V1.0//EN\n" +
"VERSION:2.0\n" +
"BEGIN:VTODO\n" +
"SUMMARY:Test Task (Exception)\n" +
"DTSTAMP;VALUE=DATE-TIME:20250228T032800Z\n" +
"DUE;TZID=America/New_York:20250228T130000\n" +
"RECURRENCE-ID;TZID=America/New_York:20250228T130000\n" +
"UID:47a23c66-8c1a-4b44-bbe8-ebf33f8cf80f\n" +
"END:VTODO\n" +
"BEGIN:VTODO\n" +
"SUMMARY:Test Task (Main VTODO)\n" +
"DTSTAMP;VALUE=DATE-TIME:20250228T032800Z\n" +
"DUE;TZID=America/New_York:20250228T130000\n" + // Due date will NOT be assumed as start for recurrence
"SEQUENCE:1\n" +
"UID:47a23c66-8c1a-4b44-bbe8-ebf33f8cf80f\n" +
"RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=FR;UNTIL=20250505T235959Z\n" +
"END:VTODO\n" +
"END:VCALENDAR"
// Create and store calendar
syncManager.processICalObject("demo-calendar", "abc123", StringReader(calendar))
// Verify main VTODO was created with RRULE present
val mainVtodo = localJtxCollection.findByName("demo-calendar")!!
assertEquals("Test Task (Main VTODO)", mainVtodo.summary)
assertEquals("FREQ=WEEKLY;UNTIL=20250505T235959Z;INTERVAL=1;BYDAY=FR", mainVtodo.rrule)
// Verify the RRULE exception instance was created with correct recurrence-id timezone
val vtodoException = localJtxCollection.findRecurInstance(
uid = "47a23c66-8c1a-4b44-bbe8-ebf33f8cf80f",
recurid = "20250228T130000"
)!!
assertEquals("Test Task (Exception)", vtodoException.summary)
assertEquals("America/New_York", vtodoException.recuridTimezone)
}
}

View File

@@ -1,9 +1,15 @@
package at.bitfire.davdroid.syncadapter
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.sync
import at.bitfire.davdroid.model.SyncState
import at.bitfire.davdroid.resource.LocalCollection
import at.bitfire.davdroid.resource.SyncState
class LocalTestCollection: LocalCollection<LocalTestResource> {
class LocalTestCollection(
override val dbCollectionId: Long = 0L
): LocalCollection<LocalTestResource> {
override val tag = "LocalTestCollection"
override val title = "Local Test Collection"
@@ -12,10 +18,13 @@ class LocalTestCollection: LocalCollection<LocalTestResource> {
val entries = mutableListOf<LocalTestResource>()
override val readOnly: Boolean
get() = throw NotImplementedError()
override fun findDeleted() = entries.filter { it.deleted }
override fun findDirty() = entries.filter { it.dirty }
override fun findByName(name: String) = entries.filter { it.fileName == name }.firstOrNull()
override fun findByName(name: String) = entries.firstOrNull { it.fileName == name }
override fun markNotDirty(flags: Int): Int {
var updated = 0

View File

@@ -0,0 +1,44 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.sync
import android.content.Context
import at.bitfire.davdroid.resource.LocalResource
import java.util.Optional
class LocalTestResource: LocalResource {
override val id: Long? = null
override var fileName: String? = null
override var eTag: String? = null
override var scheduleTag: String? = null
override var flags: Int = 0
var deleted = false
var dirty = false
override fun clearDirty(fileName: Optional<String>, eTag: String?, scheduleTag: String?) {
dirty = false
if (fileName.isPresent)
this.fileName = fileName.get()
this.eTag = eTag
this.scheduleTag = scheduleTag
}
override fun updateFlags(flags: Int) {
this.flags = flags
}
override fun updateUid(uid: String) { /* no-op */ }
override fun updateSequence(sequence: Int) = throw NotImplementedError()
override fun deleteLocal() = throw NotImplementedError()
override fun resetDeleted() = throw NotImplementedError()
override fun getDebugSummary() = "Test Resource"
override fun getViewUri(context: Context) = null
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.sync
import android.accounts.Account
import at.bitfire.dav4jvm.HttpUtils.toKtorUrl
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.sync.account.TestAccount
import at.bitfire.davdroid.util.SensitiveString.Companion.toSensitiveString
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.ktor.http.HttpHeaders
import kotlinx.coroutines.test.runTest
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.net.InetAddress
import javax.inject.Inject
@HiltAndroidTest
class ResourceDownloaderTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Inject
lateinit var accountSettingsFactory: AccountSettings.Factory
@Inject
lateinit var resourceDownloaderFactory: ResourceDownloader.Factory
lateinit var account: Account
lateinit var server: MockWebServer
@Before
fun setUp() {
hiltRule.inject()
server = MockWebServer().apply {
start()
}
account = TestAccount.create()
// add credentials to test account so that we can check whether they have been sent
val settings = accountSettingsFactory.create(account)
settings.credentials(Credentials("test", "test".toSensitiveString()))
}
@After
fun tearDown() {
TestAccount.remove(account)
server.close()
}
@Test
fun testDownload_ExternalDomain() = runTest {
val baseUrl = server.url("/")
// URL should be http://localhost, replace with http://127.0.0.1 to have other domain
Assume.assumeTrue(baseUrl.host == "localhost")
val baseUrlIp = baseUrl.newBuilder()
.host(InetAddress.getByName(baseUrl.host).hostAddress!!)
.build()
server.enqueue(MockResponse()
.setResponseCode(200)
.setBody("TEST"))
val downloader = resourceDownloaderFactory.create(account, baseUrl.host)
val result = downloader.download(baseUrlIp.toKtorUrl())
// authentication was NOT sent because request is not for original domain
val sentAuth = server.takeRequest().getHeader(HttpHeaders.Authorization)
assertNull(sentAuth)
// and result is OK
assertArrayEquals("TEST".toByteArray(), result)
}
@Test
fun testDownload_SameDomain() = runTest {
server.enqueue(MockResponse()
.setResponseCode(200)
.setBody("TEST"))
val baseUrl = server.url("/")
val downloader = resourceDownloaderFactory.create(account, baseUrl.host)
val result = downloader.download(baseUrl.toKtorUrl())
// authentication was sent
val sentAuth = server.takeRequest().getHeader(HttpHeaders.Authorization)
assertEquals("Basic dGVzdDp0ZXN0", sentAuth)
// and result is OK
assertArrayEquals("TEST".toByteArray(), result)
}
}

Some files were not shown because too many files have changed in this diff Show More