aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/phonesync/ContactSource.kt
blob: 0612e46469e340cc30d8eaa2065ba2b094ae1bc7 (plain) (blame)
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
package org.meshbay.client.phonesync

import android.Manifest
import android.content.Context
import android.net.Uri
import android.provider.ContactsContract
import org.meshbay.client.photos.PhotoChannels
import java.io.File
import java.security.MessageDigest
import java.util.TimeZone

/**
 * The phone's contacts, as the platform itself exports them: one vCard per
 * contact from ContactsContract, the format any phone imports back.
 *
 * Every run writes the whole address book and sends it only when it differs
 * from the last one sent (`marker` is its SHA-256): a file is a complete
 * copy as of its date, so restoring is importing the newest one.
 */
class ContactSource(private val context: Context) : DocSource {
    override val permissions = arrayOf(Manifest.permission.READ_CONTACTS)
    override val suffix = "contacts"

    override fun export(marker: String?, dir: File, now: Long, zone: TimeZone): Export? {
        val keys = ArrayList<String>()
        context.contentResolver.query(
            ContactsContract.Contacts.CONTENT_URI, arrayOf(ContactsContract.Contacts.LOOKUP_KEY),
            null, null, ContactsContract.Contacts._ID,
        )?.use { c -> while (c.moveToNext()) c.getString(0)?.let { keys += it } }
        if (keys.isEmpty()) return null

        val file = File(dir, "contacts.vcf")
        val digest = MessageDigest.getInstance("SHA-256")
        var count = 0
        file.outputStream().buffered().use { out ->
            for (key in keys) {
                val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, Uri.encode(key))
                // A contact removed between the list and its card is simply not in this copy.
                val bytes = try {
                    context.contentResolver.openInputStream(uri)?.use { it.readBytes() }
                } catch (e: Exception) { null } ?: continue
                out.write(bytes)
                digest.update(bytes)
                count += 1
            }
        }
        val sha = PhotoChannels.hex(digest.digest())
        if (count == 0 || sha == marker) { file.delete(); return null }
        return Export(file, DocPlan.nameFor("contacts", "vcf", now, zone), "text/vcard", sha, count)
    }
}