Skip to content

Commit 80e9902

Browse files
committed
Merge branch 'main' into ical4j-3to4
2 parents 4bfc490 + ee3e63b commit 80e9902

8 files changed

Lines changed: 269 additions & 13 deletions

File tree

.github/dependabot.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ updates:
1212
interval: "weekly"
1313
commit-message:
1414
prefix: "[CI] "
15+
labels:
16+
- "ci-cd"
17+
- "dependencies"
1518
groups:
1619
ci-actions:
1720
patterns: ["*"]
@@ -21,6 +24,8 @@ updates:
2124
directory: "/"
2225
schedule:
2326
interval: "weekly"
27+
labels: # don't create "java" label (default for gradle ecosystem)
28+
- "dependencies"
2429
groups:
2530
lib-dependencies:
2631
patterns: ["*"]

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[versions]
22
agp = "9.1.0"
33
android-desugar = "2.1.5"
4-
androidx-annotation = "1.9.1"
4+
androidx-annotation = "1.10.0"
55
androidx-core = "1.18.0"
66
androidx-test-rules = "1.7.0"
77
androidx-test-runner = "1.7.0"

lib/src/androidTest/kotlin/at/bitfire/synctools/storage/calendar/AndroidCalendarTest.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,67 @@ class AndroidCalendarTest {
128128
assertEntitiesEqual(entity, result, onlyFieldsInExpected = true)
129129
}
130130

131+
@Test
132+
fun testCountEvents_empty() {
133+
// Test counting when calendar is empty
134+
val count = calendar.countEvents(null, null)
135+
assertEquals(0, count)
136+
}
137+
138+
@Test
139+
fun testCountEvents_withEvents() {
140+
// Add multiple events and test counting
141+
calendar.addEvent(Entity(contentValuesOf(
142+
Events.CALENDAR_ID to calendar.id,
143+
Events.DTSTART to now,
144+
Events.DTEND to now + 3600000,
145+
Events.TITLE to "Event 1"
146+
)))
147+
calendar.addEvent(Entity(contentValuesOf(
148+
Events.CALENDAR_ID to calendar.id,
149+
Events.DTSTART to now + 3600000,
150+
Events.DTEND to now + 3600000*2,
151+
Events.TITLE to "Event 2"
152+
)))
153+
154+
val count = calendar.countEvents(null, null)
155+
assertEquals(2, count)
156+
}
157+
158+
@Test
159+
fun testCountEvents_filterMatch() {
160+
// Test counting with WHERE clause
161+
calendar.addEvent(Entity(contentValuesOf(
162+
Events.CALENDAR_ID to calendar.id,
163+
Events.DTSTART to now,
164+
Events.DTEND to now + 3600000,
165+
Events.TITLE to "Filter Test 1"
166+
)))
167+
calendar.addEvent(Entity(contentValuesOf(
168+
Events.CALENDAR_ID to calendar.id,
169+
Events.DTSTART to now + 3600000,
170+
Events.DTEND to now + 3600000*2,
171+
Events.TITLE to "Filter Test 2"
172+
)))
173+
174+
val filteredCount = calendar.countEvents("${Events.DTSTART}=?", arrayOf(now.toString()))
175+
assertEquals(1, filteredCount)
176+
}
177+
178+
@Test
179+
fun testCountEvents_filterNoMatch() {
180+
// Test counting with filter that matches nothing
181+
calendar.addEvent(Entity(contentValuesOf(
182+
Events.CALENDAR_ID to calendar.id,
183+
Events.DTSTART to now,
184+
Events.DTEND to now + 3600000,
185+
Events.TITLE to "Test Event"
186+
)))
187+
188+
val noMatchCount = calendar.countEvents("${Events.DTSTART}=?", arrayOf((now + 86400000).toString()))
189+
assertEquals(0, noMatchCount)
190+
}
191+
131192
@Test
132193
fun testFindEvent() {
133194
// no result

lib/src/androidTest/kotlin/at/bitfire/synctools/storage/tasks/DmfsTaskListTest.kt

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import at.bitfire.ical4android.DmfsStyleProvidersTaskTest
1414
import at.bitfire.ical4android.DmfsTask
1515
import at.bitfire.ical4android.Task
1616
import at.bitfire.ical4android.TaskProvider
17+
import junit.framework.Assert.assertTrue
18+
import junit.framework.TestCase.assertEquals
1719
import net.fortuna.ical4j.model.property.RelatedTo
1820
import org.dmfs.tasks.contract.TaskContract
19-
import org.junit.Assert
21+
import org.junit.Assert.assertNotNull
2022
import org.junit.Test
2123

2224
class DmfsTaskListTest(providerName: TaskProvider.ProviderName):
@@ -34,13 +36,73 @@ class DmfsTaskListTest(providerName: TaskProvider.ProviderName):
3436

3537
val dmfsTaskListProvider = DmfsTaskListProvider(testAccount, provider.client, providerName)
3638
val id = dmfsTaskListProvider.createTaskList(info)
37-
Assert.assertNotNull(id)
39+
assertNotNull(id)
3840

3941
dmfsTaskListProvider.createTaskList(info)
4042

4143
return dmfsTaskListProvider.getTaskList(id)!!
4244
}
4345

46+
@Test
47+
fun testCountTasks_empty() {
48+
val taskList = createTaskList()
49+
try {
50+
val count = taskList.countTasks(null, null)
51+
assertEquals(0, count)
52+
} finally {
53+
taskList.delete()
54+
}
55+
}
56+
57+
@Test
58+
fun testCountTasks_withFilter() {
59+
val taskList = createTaskList()
60+
try {
61+
// Add tasks with different UIDs
62+
val task1 = Task().apply {
63+
uid = "filter-uid-1"
64+
summary = "Filter Test 1"
65+
}
66+
val task2 = Task().apply {
67+
uid = "filter-uid-2"
68+
summary = "Filter Test 2"
69+
}
70+
71+
DmfsTask(taskList, task1, "sync-id-1", null, 0).add()
72+
DmfsTask(taskList, task2, "sync-id-2", null, 0).add()
73+
74+
// Test counting with UID filter
75+
val filteredCount = taskList.countTasks("${TaskContract.Tasks._UID}=?", arrayOf("filter-uid-1"))
76+
assertEquals(1, filteredCount)
77+
} finally {
78+
taskList.delete()
79+
}
80+
}
81+
82+
@Test
83+
fun testCountTasks_withoutFilter() {
84+
val taskList = createTaskList()
85+
try {
86+
// Add multiple tasks
87+
val task1 = Task().apply {
88+
uid = "task-1"
89+
summary = "Test Task 1"
90+
}
91+
val task2 = Task().apply {
92+
uid = "task-2"
93+
summary = "Test Task 2"
94+
}
95+
96+
DmfsTask(taskList, task1, "sync-id-1", null, 0).add()
97+
DmfsTask(taskList, task2, "sync-id-2", null, 0).add()
98+
99+
val count = taskList.countTasks(null, null)
100+
assertEquals(2, count)
101+
} finally {
102+
taskList.delete()
103+
}
104+
}
105+
44106
@Test
45107
fun testTouchRelations() {
46108
val taskList = createTaskList()
@@ -76,25 +138,25 @@ class DmfsTaskListTest(providerName: TaskProvider.ProviderName):
76138
taskList.provider.client.query(taskList.tasksPropertiesUri(), null,
77139
"${TaskContract.Properties.TASK_ID}=?", arrayOf(childId.toString()),
78140
null, null)!!.use { cursor ->
79-
Assert.assertEquals(1, cursor.count)
141+
assertEquals(1, cursor.count)
80142
cursor.moveToNext()
81143

82144
val row = ContentValues()
83145
DatabaseUtils.cursorRowToContentValues(cursor, row)
84146

85-
Assert.assertEquals(
147+
assertEquals(
86148
TaskContract.Property.Relation.CONTENT_ITEM_TYPE,
87149
row.getAsString(TaskContract.Properties.MIMETYPE)
88150
)
89-
Assert.assertEquals(
151+
assertEquals(
90152
parentId,
91153
row.getAsLong(TaskContract.Property.Relation.RELATED_ID)
92154
)
93-
Assert.assertEquals(
155+
assertEquals(
94156
parent.uid,
95157
row.getAsString(TaskContract.Property.Relation.RELATED_UID)
96158
)
97-
Assert.assertEquals(
159+
assertEquals(
98160
TaskContract.Property.Relation.RELTYPE_PARENT,
99161
row.getAsInteger(TaskContract.Property.Relation.RELATED_TYPE)
100162
)
@@ -106,8 +168,8 @@ class DmfsTaskListTest(providerName: TaskProvider.ProviderName):
106168
// now parent_id should bet set
107169
taskList.provider.client.query(childContentUri, arrayOf(TaskContract.Tasks.PARENT_ID),
108170
null, null, null)!!.use { cursor ->
109-
Assert.assertTrue(cursor.moveToNext())
110-
Assert.assertEquals(parentId, cursor.getLong(0))
171+
assertTrue(cursor.moveToNext())
172+
assertEquals(parentId, cursor.getLong(0))
111173
}
112174
} finally {
113175
taskList.delete()

lib/src/androidTest/kotlin/at/bitfire/vcard4android/AndroidAddressBookTest.kt

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@ import android.provider.ContactsContract
1414
import androidx.test.platform.app.InstrumentationRegistry
1515
import androidx.test.rule.GrantPermissionRule
1616
import at.bitfire.vcard4android.impl.TestAddressBook
17-
import org.junit.*
18-
import org.junit.Assert.*
17+
import org.junit.Assert.assertArrayEquals
18+
import org.junit.Assert.assertEquals
19+
import org.junit.Assert.assertFalse
20+
import org.junit.Assert.assertNotNull
21+
import org.junit.Assert.assertTrue
22+
import org.junit.BeforeClass
23+
import org.junit.ClassRule
24+
import org.junit.Test
1925

2026
class AndroidAddressBookTest {
2127

@@ -45,8 +51,71 @@ class AndroidAddressBookTest {
4551

4652

4753
@Test
48-
fun testSettings() {
54+
fun testCountContacts_empty() {
4955
val addressBook = TestAddressBook(testAddressBookAccount, provider)
56+
val count = addressBook.countContacts(null, null)
57+
assertEquals(0, count)
58+
}
59+
60+
@Test
61+
fun testCountContacts_withContacts() {
62+
val addressBook = TestAddressBook(testAddressBookAccount, provider)
63+
64+
// Create some test contacts
65+
val contact1 = AndroidContact(addressBook, Contact().apply {
66+
displayName = "Test Contact 1"
67+
}, null, null)
68+
contact1.add()
69+
70+
val contact2 = AndroidContact(addressBook, Contact().apply {
71+
displayName = "Test Contact 2"
72+
}, null, null)
73+
contact2.add()
74+
75+
try {
76+
val count = addressBook.countContacts(null, null)
77+
assertEquals(2, count)
78+
} finally {
79+
contact1.delete()
80+
contact2.delete()
81+
}
82+
}
83+
84+
@Test
85+
fun testCountContacts_withFilter() {
86+
val addressBook = TestAddressBook(testAddressBookAccount, provider)
87+
88+
// Create test contacts with different UIDs
89+
val contact1 = AndroidContact(addressBook, Contact().apply {
90+
displayName = "Filter Test 1"
91+
uid = "test-uid-1"
92+
}, null, null)
93+
contact1.add()
94+
95+
val contact2 = AndroidContact(addressBook, Contact().apply {
96+
displayName = "Filter Test 2"
97+
uid = "test-uid-2"
98+
}, null, null)
99+
contact2.add()
100+
101+
try {
102+
// Test counting with filter
103+
val filteredCount = addressBook.countContacts("${AndroidContact.COLUMN_UID}=?", arrayOf("test-uid-1"))
104+
assertEquals(1, filteredCount)
105+
106+
// Test counting with non-matching filter
107+
val noMatchCount = addressBook.countContacts("${AndroidContact.COLUMN_UID}=?", arrayOf("non-existent"))
108+
assertEquals(0, noMatchCount)
109+
} finally {
110+
contact1.delete()
111+
contact2.delete()
112+
}
113+
}
114+
115+
116+
@Test
117+
fun testSettings() {
118+
val addressBook = TestAddressBook(testAddressBookAccount, provider)
50119

51120
var values = ContentValues()
52121
values.put(ContactsContract.Settings.SHOULD_SYNC, false)

lib/src/main/kotlin/at/bitfire/synctools/storage/calendar/AndroidCalendar.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,28 @@ class AndroidCalendar(
114114
.withValueBackReference(EventsContract.DATA_ROW_EVENT_ID, eventRowIdx)
115115
}
116116

117+
/**
118+
* Counts the number of events in this calendar that match the given selection criteria.
119+
*
120+
* @param where An optional filter declaring which rows to return.
121+
* @param whereArgs Optional arguments for [where].
122+
* @return The number of events matching the selection criteria.
123+
* @throws LocalStorageException when the content provider returns an error
124+
*/
125+
fun countEvents(where: String?, whereArgs: Array<String>?): Int {
126+
try {
127+
val (protectedWhere, protectedWhereArgs) = whereWithCalendarId(where, whereArgs)
128+
client.query(eventsUri, arrayOf(Events._ID),
129+
protectedWhere, protectedWhereArgs, null)?.use { cursor ->
130+
return cursor.count
131+
}
132+
} catch (e: RemoteException) {
133+
throw LocalStorageException("Couldn't count events", e)
134+
}
135+
// If the query was invalid, an exception should have been thrown. So this should never be reached:
136+
return 0
137+
}
138+
117139
/**
118140
* Gets the first event from this calendar that matches the given query.
119141
*

lib/src/main/kotlin/at/bitfire/synctools/storage/tasks/DmfsTaskList.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,28 @@ class DmfsTaskList(
5252

5353
// CRUD DmfsTask
5454

55+
/**
56+
* Counts the number of tasks in this task list that match the given selection criteria.
57+
*
58+
* @param where An optional filter declaring which rows to return.
59+
* @param whereArgs Optional arguments for [where].
60+
* @return The number of tasks matching the selection criteria.
61+
* @throws LocalStorageException when the content provider returns an error
62+
*/
63+
fun countTasks(where: String? = null, whereArgs: Array<String>? = null): Int {
64+
try {
65+
val (protectedWhere, protectedWhereArgs) = whereWithTaskListId(where, whereArgs)
66+
client.query(tasksUri(), arrayOf(TaskContract.Tasks._ID),
67+
protectedWhere, protectedWhereArgs, null)?.use { cursor ->
68+
return cursor.count
69+
}
70+
} catch (e: RemoteException) {
71+
throw LocalStorageException("Couldn't count ${providerName.authority} tasks", e)
72+
}
73+
// If the query was invalid, an exception should have been thrown. So this should never be reached:
74+
return 0
75+
}
76+
5577
/**
5678
* Queries tasks from this task list. Adds a WHERE clause that restricts the
5779
* query to [TaskContract.TaskColumns.LIST_ID] = [id].

lib/src/main/kotlin/at/bitfire/vcard4android/AndroidAddressBook.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ open class AndroidAddressBook<T1: AndroidContact, T2: AndroidGroup>(
5656
get() = ContactsContract.SyncState.get(provider, addressBookAccount)
5757
set(data) = ContactsContract.SyncState.set(provider, addressBookAccount, data)
5858

59+
/**
60+
* Counts the number of contacts in the address book that match the given selection criteria.
61+
*
62+
* @param where An optional filter declaring which rows to return.
63+
* @param whereArgs Optional arguments for [where].
64+
* @return The number of contacts matching the selection criteria.
65+
*/
66+
fun countContacts(where: String?, whereArgs: Array<String>?): Int {
67+
provider!!.query(rawContactsSyncUri(), arrayOf(RawContacts._ID),
68+
where, whereArgs, null)?.use { cursor ->
69+
return cursor.count
70+
}
71+
// If the query was invalid, an exception should have been thrown. So this should never be reached:
72+
return 0
73+
}
5974

6075
fun queryContacts(where: String?, whereArgs: Array<String>?): List<T1> {
6176
val contacts = LinkedList<T1>()

0 commit comments

Comments
 (0)