1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
/*
* 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.vcard4android
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentValues
import android.net.Uri
import android.provider.ContactsContract
import android.provider.ContactsContract.Groups
import android.provider.ContactsContract.RawContacts
import at.bitfire.vcard4android.Utils.toContentValues
import java.io.FileNotFoundException
import java.util.*
import kotlin.jvm.Throws
open class AndroidAddressBook<T1: AndroidContact, T2: AndroidGroup>(
var account: Account,
val provider: ContentProviderClient?,
protected val contactFactory: AndroidContactFactory<T1>,
protected val groupFactory: AndroidGroupFactory<T2>
) {
open var readOnly: Boolean = false
var settings: ContentValues
/**
* Retrieves [ContactsContract.Settings] for the current address book.
* @throws FileNotFoundException if the settings row couldn't be fetched.
* @throws android.os.RemoteException on content provider errors
*/
get() {
provider!!.query(syncAdapterURI(ContactsContract.Settings.CONTENT_URI), null, null, null, null)?.use { cursor ->
if (cursor.moveToNext())
return cursor.toContentValues()
}
throw FileNotFoundException()
}
/**
* Updates [ContactsContract.Settings] by inserting the given values into
* the current address book.
* @param values settings to be updated
* @throws android.os.RemoteException on content provider errors
*/
set(values) {
values.put(ContactsContract.Settings.ACCOUNT_NAME, account.name)
values.put(ContactsContract.Settings.ACCOUNT_TYPE, account.type)
provider!!.insert(syncAdapterURI(ContactsContract.Settings.CONTENT_URI), values)
}
var syncState: ByteArray?
get() = ContactsContract.SyncState.get(provider, account)
set(data) = ContactsContract.SyncState.set(provider, account, data)
fun queryContacts(where: String?, whereArgs: Array<String>?): List<T1> {
val contacts = LinkedList<T1>()
provider!!.query(rawContactsSyncUri(),
null, where, whereArgs, null)?.use { cursor ->
while (cursor.moveToNext())
contacts += contactFactory.fromProvider(this, cursor.toContentValues())
}
return contacts
}
fun queryGroups(where: String?, whereArgs: Array<String>?): List<T2> {
val groups = LinkedList<T2>()
provider!!.query(groupsSyncUri(),
arrayOf(Groups._ID, AndroidGroup.COLUMN_FILENAME, AndroidGroup.COLUMN_ETAG),
where, whereArgs, null)?.use { cursor ->
while (cursor.moveToNext())
groups += groupFactory.fromProvider(this, cursor.toContentValues())
}
return groups
}
@Throws(FileNotFoundException::class)
fun findContactById(id: Long) =
queryContacts("${RawContacts._ID}=?", arrayOf(id.toString())).firstOrNull() ?: throw FileNotFoundException()
fun findContactByUid(uid: String) =
queryContacts("${AndroidContact.COLUMN_UID}=?", arrayOf(uid)).firstOrNull()
@Throws(FileNotFoundException::class)
fun findGroupById(id: Long) =
queryGroups("${Groups._ID}=?", arrayOf(id.toString())).firstOrNull() ?: throw FileNotFoundException()
// helpers
fun syncAdapterURI(uri: Uri) = uri.buildUpon()
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
.build()!!
fun rawContactsSyncUri() = syncAdapterURI(RawContacts.CONTENT_URI)
fun groupsSyncUri() = syncAdapterURI(Groups.CONTENT_URI)
}
|