Penyedia Kontak

Penyedia Kontak adalah komponen Android yang andal dan fleksibel yang mengelola repositori pusat data tentang orang di perangkat. Penyedia Kontak adalah sumber data yang Anda lihat di aplikasi kontak perangkat, dan Anda juga dapat mengakses datanya di aplikasi Anda sendiri dan mentransfer data antara perangkat dan layanan online. Penyedia mengakomodasi berbagai sumber data dan mencoba mengelola data sebanyak mungkin untuk setiap orang, sehingga organisasinya menjadi kompleks. Oleh karena itu, API penyedia menyertakan sekumpulan class dan antarmuka kontrak yang ekstensif yang memfasilitasi pengambilan dan modifikasi data.

Panduan ini menjelaskan hal-hal berikut:

  • Struktur penyedia dasar.
  • Cara mengambil data dari penyedia.
  • Cara mengubah data di penyedia.
  • Cara menulis adaptor sinkronisasi untuk menyinkronkan data dari server Anda ke Penyedia Kontak.

Panduan ini mengasumsikan bahwa Anda mengetahui dasar-dasar penyedia konten Android. Untuk mempelajari lebih lanjut penyedia konten Android, baca panduan Dasar-dasar Penyedia Konten.

Organisasi Penyedia Kontak

Penyedia Kontak adalah komponen penyedia konten Android. Penyedia ini mengelola tiga jenis data tentang seseorang, yang masing-masing sesuai dengan tabel yang ditawarkan oleh penyedia, seperti yang diilustrasikan dalam gambar 1:

Gambar 1. Struktur tabel Penyedia Kontak.

Ketiga tabel tersebut biasanya disebut dengan nama class kontraknya. Class menentukan konstanta untuk URI konten, nama kolom, dan nilai kolom yang digunakan oleh tabel:

Tabel ContactsContract.Contacts
Baris yang mewakili orang yang berbeda, berdasarkan agregasi baris kontak mentah.
Tabel ContactsContract.RawContacts
Baris yang berisi ringkasan data seseorang, khusus untuk akun dan jenis pengguna.
Tabel ContactsContract.Data
Baris yang berisi detail untuk kontak mentah, seperti alamat email atau nomor telepon.

Tabel lain yang diwakili oleh class kontrak di ContactsContract adalah tabel tambahan yang digunakan Penyedia Kontak untuk mengelola operasinya atau mendukung fungsi tertentu dalam aplikasi kontak atau telepon perangkat.

Kontak mentah

Kontak mentah mewakili data seseorang yang berasal dari satu jenis akun dan nama akun. Karena Penyedia Kontak mengizinkan lebih dari satu layanan online sebagai sumber data untuk seseorang, Penyedia Kontak mengizinkan beberapa kontak mentah untuk orang yang sama. Beberapa kontak mentah juga memungkinkan pengguna menggabungkan data seseorang dari lebih dari satu akun dari jenis akun yang sama.

Sebagian besar data untuk kontak mentah tidak disimpan dalam tabel ContactsContract.RawContacts. Namun, data tersebut disimpan dalam satu atau beberapa baris di tabel ContactsContract.Data. Setiap baris data memiliki kolom Data.RAW_CONTACT_ID yang berisi nilai RawContacts._ID dari baris ContactsContract.RawContacts induknya.

Kolom kontak mentah yang penting

Kolom penting dalam tabel ContactsContract.RawContacts tercantum dalam tabel 1. Baca catatan yang ada setelah tabel:

Tabel 1. Kolom kontak mentah yang penting.

Nama kolom Gunakan Catatan
ACCOUNT_NAME Nama akun untuk jenis akun yang merupakan sumber kontak mentah ini. Misalnya, nama akun Akun Google adalah salah satu alamat Gmail pemilik perangkat. Lihat entri berikutnya untuk ACCOUNT_TYPE guna mengetahui informasi selengkapnya. Format nama ini khusus untuk jenis akunnya. ID ini tidak harus berupa alamat email.
ACCOUNT_TYPE Jenis akun yang merupakan sumber kontak mentah ini. Misalnya, jenis akun Akun Google adalah com.google. Selalu penuhi syarat jenis akun Anda dengan ID domain untuk domain yang Anda miliki atau kontrol. Tindakan ini akan memastikan bahwa jenis akun Anda unik. Jenis akun yang menawarkan data kontak biasanya memiliki adaptor sinkronisasi terkait yang disinkronkan dengan Penyedia Kontak.
DELETED Tanda "dihapus" untuk kontak mentah. Flag ini memungkinkan Penyedia Kontak mempertahankan baris secara internal hingga adaptor sinkronisasi dapat menghapus baris dari servernya, lalu akhirnya menghapus baris dari repositori.

Catatan

Berikut adalah catatan penting tentang tabel ContactsContract.RawContacts:

  • Nama kontak mentah tidak disimpan di barisnya di ContactsContract.RawContacts. Sebagai gantinya, data ini disimpan dalam tabel ContactsContract.Data, di baris ContactsContract.CommonDataKinds.StructuredName. Kontak mentah hanya memiliki satu baris jenis ini dalam tabel ContactsContract.Data.
  • Perhatian: Untuk menggunakan data akun Anda sendiri di baris kontak mentah, data tersebut harus didaftarkan terlebih dahulu ke AccountManager. Untuk melakukannya, minta pengguna untuk menambahkan jenis akun dan nama akun mereka ke daftar akun. Jika Anda tidak melakukannya, Penyedia Kontak akan otomatis menghapus baris kontak mentah Anda.

    Misalnya, jika Anda ingin aplikasi mempertahankan data kontak untuk layanan berbasis web dengan domain com.example.dataservice, dan akun pengguna untuk layanan Anda adalah becky.sharp@dataservice.example.com, pengguna harus menambahkan "jenis" akun (com.example.dataservice) dan "nama" akun (becky.smart@dataservice.example.com) terlebih dahulu sebelum aplikasi Anda dapat menambahkan baris kontak mentah. Anda dapat menjelaskan persyaratan ini kepada pengguna dalam dokumentasi, atau Anda dapat meminta pengguna untuk menambahkan jenis dan nama, atau keduanya. Jenis akun dan nama akun dijelaskan secara lebih mendetail di bagian berikutnya.

Sumber data kontak mentah

Untuk memahami cara kerja kontak mentah, pertimbangkan pengguna "Emily Dickinson" yang memiliki tiga akun pengguna berikut yang ditentukan di perangkatnya:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Akun Twitter "belle_of_amherst"

Pengguna ini telah mengaktifkan Sinkronkan Kontak untuk ketiga akun ini di setelan Akun.

Misalnya, Emily Dickinson membuka jendela browser, login ke Gmail sebagai emily.dickinson@gmail.com, membuka Kontak, dan menambahkan "Thomas Higginson". Kemudian, dia login ke Gmail sebagai emilyd@gmail.com dan mengirim email ke "Thomas Higginson", yang otomatis menambahkannya sebagai kontak. Dia juga mengikuti "colonel_tom" (ID Twitter Thomas Higginson) di Twitter.

Penyedia Kontak membuat tiga kontak mentah sebagai hasil dari pekerjaan ini:

  1. Kontak mentah untuk "Thomas Higginson" yang terkait dengan emily.dickinson@gmail.com. Jenis akun pengguna adalah Google.
  2. Kontak mentah kedua untuk "Thomas Higginson" yang terkait dengan emilyd@gmail.com. Jenis akun pengguna juga Google. Ada kontak mentah kedua meskipun namanya sama dengan nama sebelumnya, karena orang tersebut ditambahkan untuk akun pengguna yang berbeda.
  3. Kontak mentah ketiga untuk "Thomas Higginson" yang terkait dengan "belle_of_amherst". Jenis akun pengguna adalah Twitter.

Data

Seperti yang telah disebutkan sebelumnya, data untuk kontak mentah disimpan dalam baris ContactsContract.Data yang ditautkan ke nilai _ID kontak mentah. Hal ini memungkinkan satu kontak mentah memiliki beberapa instance dari jenis data yang sama, seperti alamat email atau nomor telepon. Misalnya, jika "Thomas Higginson" untuk emilyd@gmail.com (baris kontak mentah untuk Thomas Higginson yang terkait dengan Akun Google emilyd@gmail.com) memiliki alamat email rumah thigg@gmail.com dan alamat email kantor thomas.higginson@gmail.com, Penyedia Kontak akan menyimpan dua baris alamat email dan menautkan keduanya ke kontak mentah.

Perhatikan bahwa berbagai jenis data disimpan dalam satu tabel ini. Baris detail nama tampilan, nomor telepon, email, alamat pos, foto, dan situs semuanya ditemukan dalam tabel ContactsContract.Data. Untuk membantu mengelola hal ini, tabel ContactsContract.Data memiliki beberapa kolom dengan nama deskriptif, dan kolom lainnya dengan nama umum. Konten kolom nama deskriptif memiliki arti yang sama, terlepas dari jenis data dalam baris, sedangkan konten kolom nama umum memiliki arti yang berbeda, bergantung pada jenis data.

Nama kolom deskriptif

Beberapa contoh nama kolom deskriptif adalah:

RAW_CONTACT_ID
Nilai kolom _ID dari kontak mentah untuk data ini.
MIMETYPE
Jenis data yang disimpan di baris ini, dinyatakan sebagai jenis MIME kustom. Penyedia Kontak menggunakan jenis MIME yang ditentukan dalam subclass ContactsContract.CommonDataKinds. Jenis MIME ini bersifat open source, dan dapat digunakan oleh aplikasi atau adaptor sinkronisasi apa pun yang berfungsi dengan Penyedia Kontak.
IS_PRIMARY
Jika jenis baris data ini dapat terjadi lebih dari sekali untuk kontak mentah, kolom IS_PRIMARY akan menandai baris data yang berisi data utama untuk jenis tersebut. Misalnya, jika pengguna menekan lama nomor telepon untuk kontak dan memilih Tetapkan sebagai default, maka baris ContactsContract.Data yang berisi nomor tersebut akan memiliki kolom IS_PRIMARY yang ditetapkan ke nilai bukan nol.

Nama kolom umum

Ada 15 kolom generik bernama DATA1 hingga DATA15 yang tersedia secara umum dan empat kolom generik tambahan SYNC1 hingga SYNC4 yang hanya boleh digunakan oleh adaptor sinkronisasi. Konstanta nama kolom generik selalu berfungsi, terlepas dari jenis data yang ada di baris.

Kolom DATA1 diindeks. Penyedia Kontak selalu menggunakan kolom ini untuk data yang diperkirakan oleh penyedia akan menjadi target kueri yang paling sering. Misalnya, di baris email, kolom ini berisi alamat email yang sebenarnya.

Berdasarkan konvensi, kolom DATA15 dicadangkan untuk menyimpan data Binary Large Object (BLOB) seperti thumbnail foto.

Nama kolom khusus jenis

Untuk memfasilitasi penggunaan kolom untuk jenis baris tertentu, Penyedia Kontak juga menyediakan konstanta nama kolom khusus jenis, yang ditentukan dalam subclass ContactsContract.CommonDataKinds. Konstanta hanya memberikan nama konstanta yang berbeda ke nama kolom yang sama, yang membantu Anda mengakses data dalam baris jenis tertentu.

Misalnya, class ContactsContract.CommonDataKinds.Email menentukan konstanta nama kolom khusus jenis untuk baris ContactsContract.Data yang memiliki jenis MIME Email.CONTENT_ITEM_TYPE. Class ini berisi konstanta ADDRESS untuk kolom alamat email. Nilai sebenarnya dari ADDRESS adalah "data1", yang sama dengan nama umum kolom.

Perhatian: Jangan tambahkan data kustom Anda sendiri ke tabel ContactsContract.Data menggunakan baris yang memiliki salah satu jenis MIME yang telah ditentukan penyedia. Jika melakukannya, Anda dapat kehilangan data atau menyebabkan penyedia tidak berfungsi. Misalnya, Anda tidak boleh menambahkan baris dengan jenis MIME Email.CONTENT_ITEM_TYPE yang berisi nama pengguna, bukan alamat email di kolom DATA1. Jika menggunakan jenis MIME kustom Anda sendiri untuk baris, Anda bebas menentukan nama kolom khusus jenis Anda sendiri dan menggunakan kolom tersebut sesuai keinginan.

Gambar 2 menunjukkan tampilan kolom deskriptif dan kolom data dalam baris ContactsContract.Data, serta cara nama kolom khusus jenis "menimpa" nama kolom umum

Cara nama kolom khusus jenis dipetakan ke nama kolom umum

Gambar 2. Nama kolom khusus jenis dan nama kolom umum.

Class nama kolom khusus jenis

Tabel 2 mencantumkan class nama kolom khusus jenis yang paling umum digunakan:

Tabel 2. Class nama kolom khusus jenis

Class pemetaan Jenis data Catatan
ContactsContract.CommonDataKinds.StructuredName Data nama untuk kontak mentah yang terkait dengan baris data ini. Kontak mentah hanya memiliki satu baris ini.
ContactsContract.CommonDataKinds.Photo Foto utama untuk kontak mentah yang terkait dengan baris data ini. Kontak mentah hanya memiliki satu baris ini.
ContactsContract.CommonDataKinds.Email Alamat email untuk kontak mentah yang terkait dengan baris data ini. Kontak mentah dapat memiliki beberapa alamat email.
ContactsContract.CommonDataKinds.StructuredPostal Alamat pos untuk kontak mentah yang terkait dengan baris data ini. Kontak mentah dapat memiliki beberapa alamat pos.
ContactsContract.CommonDataKinds.GroupMembership ID yang menautkan kontak mentah ke salah satu grup di Penyedia Kontak. Grup adalah fitur opsional dari jenis akun dan nama akun. Grup ini dijelaskan secara lebih mendetail di bagian Grup kontak.

Kontak

Penyedia Kontak menggabungkan baris kontak mentah di semua jenis akun dan nama akun untuk membentuk kontak. Hal ini memfasilitasi tampilan dan modifikasi semua data yang telah dikumpulkan pengguna untuk seseorang. Penyedia Kontak mengelola pembuatan baris kontak baru, dan penggabungan kontak mentah dengan baris kontak yang ada. Baik aplikasi maupun adaptor sinkronisasi tidak diizinkan untuk menambahkan kontak, dan beberapa kolom dalam baris kontak bersifat hanya baca.

Catatan: Jika Anda mencoba menambahkan kontak ke Penyedia Kontak dengan insert(), Anda akan mendapatkan pengecualian UnsupportedOperationException. Jika Anda mencoba memperbarui kolom yang tercantum sebagai "hanya baca", pembaruan akan diabaikan.

Penyedia Kontak membuat kontak baru sebagai respons terhadap penambahan kontak mentah baru yang tidak cocok dengan kontak yang ada. Penyedia juga melakukan ini jika data kontak mentah yang ada berubah sedemikian rupa sehingga tidak lagi cocok dengan kontak yang sebelumnya terlampir. Jika aplikasi atau adaptor sinkronisasi membuat kontak mentah baru yang cocok dengan kontak yang ada, kontak mentah baru tersebut akan digabungkan ke kontak yang ada.

Penyedia Kontak menautkan baris kontak ke baris kontak mentahnya dengan kolom _ID baris kontak di tabel Contacts. Kolom CONTACT_ID dari tabel kontak mentah ContactsContract.RawContacts berisi nilai _ID untuk baris kontak yang terkait dengan setiap baris kontak mentah.

Tabel ContactsContract.Contacts juga memiliki kolom LOOKUP_KEY yang merupakan link "permanen" ke baris kontak. Karena Penyedia Kontak mengelola kontak secara otomatis, penyedia ini dapat mengubah nilai _ID baris kontak sebagai respons terhadap agregasi atau sinkronisasi. Meskipun hal ini terjadi, URI konten CONTENT_LOOKUP_URI yang digabungkan dengan LOOKUP_KEY kontak akan tetap mengarah ke baris kontak, sehingga Anda dapat menggunakan LOOKUP_KEY untuk mempertahankan link ke kontak "favorit", dan sebagainya. Kolom ini memiliki formatnya sendiri yang tidak terkait dengan format kolom _ID.

Gambar 3 menunjukkan hubungan antara tiga tabel utama.

Tabel utama penyedia kontak

Gambar 3. Hubungan tabel Kontak, Kontak Mentah, dan Detail.

Perhatian: Jika Anda memublikasikan aplikasi ke Google Play Store, atau jika aplikasi Anda berada di perangkat yang menjalankan Android 10 (API level 29) atau yang lebih tinggi, perlu diingat bahwa sejumlah kecil kolom dan metode data kontak sudah tidak berlaku.

Dalam kondisi yang disebutkan, sistem akan menghapus secara berkala nilai apa pun yang ditulis ke kolom data ini:

API yang digunakan untuk menetapkan kolom data di atas juga sudah tidak digunakan lagi:

Selain itu, kolom berikut tidak lagi menampilkan kontak yang sering dihubungi. Perhatikan bahwa beberapa kolom ini hanya memengaruhi peringkat kontak jika kontak tersebut merupakan bagian dari jenis data tertentu.

Jika aplikasi Anda mengakses atau memperbarui kolom atau API ini, gunakan metode alternatif. Misalnya, Anda dapat memenuhi kasus penggunaan tertentu dengan menggunakan penyedia konten pribadi atau data lain yang disimpan dalam aplikasi atau sistem backend Anda.

Untuk memverifikasi bahwa fungsi aplikasi Anda tidak terpengaruh oleh perubahan ini, Anda dapat menghapus kolom data ini secara manual. Untuk melakukannya, jalankan perintah ADB berikut di perangkat yang menjalankan Android 4.1 (API level 16) atau yang lebih tinggi:

adb shell content delete \
--uri content://com.android.contacts/contacts/delete_usage

Data Dari adaptor sinkronisasi

Pengguna memasukkan data kontak langsung ke perangkat, tetapi data juga mengalir ke Penyedia Kontak dari layanan web melalui adaptor sinkronisasi, yang mengotomatiskan transfer data antara perangkat dan layanan. Adaptor sinkronisasi berjalan di latar belakang di bawah kontrol sistem, dan memanggil metode ContentResolver untuk mengelola data.

Di Android, layanan web yang digunakan oleh adaptor sinkronisasi diidentifikasi oleh jenis akun. Setiap adaptor sinkronisasi berfungsi dengan satu jenis akun, tetapi dapat mendukung beberapa nama akun untuk jenis tersebut. Jenis akun dan nama akun dijelaskan secara singkat di bagian Sumber data kontak mentah. Definisi berikut menawarkan detail selengkapnya, dan menjelaskan hubungan jenis dan nama akun dengan adaptor dan layanan sinkronisasi.

Jenis akun
Mengidentifikasi layanan tempat pengguna menyimpan data. Sering kali, pengguna harus melakukan autentikasi dengan layanan. Misalnya, Google Kontak adalah jenis akun, yang diidentifikasi dengan kode google.com. Nilai ini sesuai dengan jenis akun yang digunakan oleh AccountManager.
Nama akun
Mengidentifikasi akun atau login tertentu untuk jenis akun. Akun Google Kontak sama dengan Akun Google, yang memiliki alamat email sebagai nama akun. Layanan lain mungkin menggunakan nama pengguna satu kata atau ID numerik.

Jenis akun tidak harus unik. Pengguna dapat mengonfigurasi beberapa akun Google Kontak dan mendownload datanya ke Penyedia Kontak; hal ini dapat terjadi jika pengguna memiliki satu set kontak pribadi untuk nama akun pribadi, dan set lainnya untuk kantor. Nama akun biasanya unik. Bersama-sama, keduanya mengidentifikasi aliran data tertentu antara Penyedia Kontak dan layanan eksternal.

Jika ingin mentransfer data layanan ke Penyedia Kontak, Anda harus menulis adaptor sinkronisasi Anda sendiri. Hal ini dijelaskan secara lebih mendetail di bagian Adaptor sinkronisasi Penyedia Kontak.

Gambar 4 menunjukkan cara Penyedia Kontak menyesuaikan dengan alur data tentang orang. Dalam kotak yang ditandai "adaptor sinkronisasi", setiap adaptor diberi label berdasarkan jenis akunnya.

Aliran data tentang orang

Gambar 4. Alur data Penyedia Kontak.

Izin yang diperlukan

Aplikasi yang ingin mengakses Penyedia Kontak harus meminta izin berikut:

Akses baca ke satu atau beberapa tabel
READ_CONTACTS, ditentukan dalam AndroidManifest.xml dengan elemen <uses-permission> sebagai <uses-permission android:name="android.permission.READ_CONTACTS">.
Akses tulis ke satu atau beberapa tabel
WRITE_CONTACTS, ditentukan dalam AndroidManifest.xml dengan elemen <uses-permission> sebagai <uses-permission android:name="android.permission.WRITE_CONTACTS">.

Izin ini tidak berlaku untuk data profil pengguna. Profil pengguna dan izin yang diperlukan dibahas di bagian berikut, Profil pengguna.

Ingat bahwa data kontak pengguna bersifat pribadi dan sensitif. Pengguna mengkhawatirkan privasi mereka, sehingga mereka tidak ingin aplikasi mengumpulkan data tentang mereka atau kontak mereka. Jika tidak jelas mengapa Anda memerlukan izin untuk mengakses data kontak mereka, mereka mungkin memberikan rating rendah pada aplikasi Anda atau menolak untuk menginstalnya.

Profil pengguna

Tabel ContactsContract.Contacts memiliki satu baris yang berisi data profil untuk pengguna perangkat. Data ini menjelaskan user perangkat, bukan salah satu kontak pengguna. Baris kontak profil ditautkan ke baris kontak mentah untuk setiap sistem yang menggunakan profil. Setiap baris kontak mentah profil dapat memiliki beberapa baris data. Konstanta untuk mengakses profil pengguna tersedia di class ContactsContract.Profile.

Akses ke profil pengguna memerlukan izin khusus. Selain izin READ_CONTACTS dan WRITE_CONTACTS yang diperlukan untuk membaca dan menulis, akses ke profil pengguna memerlukan izin android.Manifest.permission#READ_PROFILE dan android.Manifest.permission#WRITE_PROFILE untuk akses baca dan tulis, masing-masing.

Ingatlah bahwa Anda harus menganggap profil pengguna sebagai data sensitif. Izin android.Manifest.permission#READ_PROFILE memungkinkan Anda mengakses data identitas pribadi pengguna perangkat. Pastikan untuk memberi tahu pengguna alasan Anda memerlukan izin akses profil pengguna dalam deskripsi aplikasi Anda.

Untuk mengambil baris kontak yang berisi profil pengguna, panggil ContentResolver.query(). Tetapkan URI konten ke CONTENT_URI dan jangan berikan kriteria pemilihan apa pun. Anda juga dapat menggunakan URI konten ini sebagai URI dasar untuk mengambil kontak mentah atau data untuk profil. Misalnya, cuplikan ini mengambil data untuk profil:

Kotlin

// Sets the columns to retrieve for the user profile
projection = arrayOf(
        ContactsContract.Profile._ID,
        ContactsContract.Profile.DISPLAY_NAME_PRIMARY,
        ContactsContract.Profile.LOOKUP_KEY,
        ContactsContract.Profile.PHOTO_THUMBNAIL_URI
)

// Retrieves the profile from the Contacts Provider
profileCursor = contentResolver.query(
        ContactsContract.Profile.CONTENT_URI,
        projection,
        null,
        null,
        null
)

Java

// Sets the columns to retrieve for the user profile
projection = new String[]
    {
        Profile._ID,
        Profile.DISPLAY_NAME_PRIMARY,
        Profile.LOOKUP_KEY,
        Profile.PHOTO_THUMBNAIL_URI
    };

// Retrieves the profile from the Contacts Provider
profileCursor =
        getContentResolver().query(
                Profile.CONTENT_URI,
                projection ,
                null,
                null,
                null);

Catatan: Jika Anda mengambil beberapa baris kontak, dan ingin menentukan apakah salah satunya adalah profil pengguna, uji kolom IS_USER_PROFILE baris tersebut. Kolom ini disetel ke "1" jika kontak adalah profil pengguna.

Metadata Penyedia Kontak

Penyedia Kontak mengelola data yang melacak status data kontak di repositori. Metadata tentang repositori ini disimpan di berbagai tempat, termasuk baris tabel Kontak Mentah, Data, dan Kontak, tabel ContactsContract.Settings, dan tabel ContactsContract.SyncState. Tabel berikut menunjukkan efek dari setiap bagian metadata ini:

Tabel 3. Metadata di Penyedia Kontak

Tabel Kolom Nilai Arti
ContactsContract.RawContacts DIRTY "0" - tidak berubah sejak sinkronisasi terakhir. Menandai kontak mentah yang diubah di perangkat dan harus disinkronkan kembali ke server. Nilai ditetapkan secara otomatis oleh Penyedia Kontak saat aplikasi Android mengupdate baris.

Adaptor sinkronisasi yang mengubah tabel data atau kontak mentah harus selalu menambahkan string CALLER_IS_SYNCADAPTER ke URI konten yang digunakan. Hal ini mencegah penyedia menandai baris sebagai kotor. Jika tidak, modifikasi adaptor sinkronisasi akan tampak sebagai modifikasi lokal dan dikirim ke server, meskipun server adalah sumber modifikasi.

"1" - diubah sejak sinkronisasi terakhir, perlu disinkronkan kembali ke server.
ContactsContract.RawContacts VERSION Nomor versi baris ini. Penyedia Kontak otomatis menambah nilai ini setiap kali baris atau data terkaitnya berubah.
ContactsContract.Data DATA_VERSION Nomor versi baris ini. Penyedia Kontak otomatis menambah nilai ini setiap kali baris data diubah.
ContactsContract.RawContacts SOURCE_ID Nilai string yang secara unik mengidentifikasi kontak mentah ini ke akun tempat kontak tersebut dibuat. Saat adaptor sinkronisasi membuat kontak mentah baru, kolom ini harus ditetapkan ke ID unik server untuk kontak mentah. Saat aplikasi Android membuat kontak mentah baru, aplikasi harus mengosongkan kolom ini. Hal ini memberi sinyal kepada adaptor sinkronisasi bahwa adaptor harus membuat kontak mentah baru di server, dan mendapatkan nilai untuk SOURCE_ID.

Secara khusus, ID sumber harus unik untuk setiap jenis akun dan harus stabil di seluruh sinkronisasi:

  • Unik: Setiap kontak mentah untuk akun harus memiliki ID sumbernya sendiri. Jika Anda tidak menerapkan ini, Anda akan menyebabkan masalah dalam aplikasi kontak. Perhatikan bahwa dua kontak mentah untuk jenis akun yang sama mungkin memiliki ID sumber yang sama. Misalnya, kontak mentah "Thomas Higginson" untuk akun emily.dickinson@gmail.com diizinkan memiliki ID sumber yang sama dengan kontak mentah "Thomas Higginson" untuk akun emilyd@gmail.com.
  • Stabil: ID sumber adalah bagian permanen dari data layanan online untuk kontak mentah. Misalnya, jika pengguna menghapus Penyimpanan Kontak dari setelan Aplikasi dan menyinkronkan ulang, kontak mentah yang dipulihkan harus memiliki ID sumber yang sama seperti sebelumnya. Jika Anda tidak menerapkan ini, pintasan akan berhenti berfungsi.
ContactsContract.Groups GROUP_VISIBLE "0" - Kontak dalam grup ini tidak boleh terlihat di UI aplikasi Android. Kolom ini ditujukan untuk kompatibilitas dengan server yang memungkinkan pengguna menyembunyikan kontak di grup tertentu.
"1" - Kontak dalam grup ini diizinkan untuk terlihat di UI aplikasi.
ContactsContract.Settings UNGROUPED_VISIBLE "0" - Untuk akun dan jenis akun ini, kontak yang bukan milik grup tidak akan terlihat oleh UI aplikasi Android. Secara default, kontak tidak terlihat jika tidak ada kontak mentahnya yang termasuk dalam grup (Keanggotaan grup untuk kontak mentah ditunjukkan oleh satu atau beberapa baris ContactsContract.CommonDataKinds.GroupMembership dalam tabel ContactsContract.Data). Dengan menetapkan tanda ini di baris tabel ContactsContract.Settings untuk jenis akun dan akun, Anda dapat memaksa kontak tanpa grup agar terlihat. Salah satu penggunaan flag ini adalah untuk menampilkan kontak dari server yang tidak menggunakan grup.
"1" - Untuk akun dan jenis akun ini, kontak yang bukan milik grup terlihat oleh UI aplikasi.
ContactsContract.SyncState (semua) Gunakan tabel ini untuk menyimpan metadata adaptor sinkronisasi Anda. Dengan tabel ini, Anda dapat menyimpan status sinkronisasi dan data terkait sinkronisasi lainnya secara persisten di perangkat.

Akses Penyedia Kontak

Bagian ini menjelaskan panduan untuk mengakses data dari Penyedia Kontak, dengan berfokus pada hal berikut:

  • Kueri entity.
  • Modifikasi batch.
  • Pengambilan dan modifikasi dengan intent.
  • Integritas data.

Melakukan modifikasi dari adaptor sinkronisasi juga dibahas secara lebih mendetail di bagian Adaptor sinkronisasi Penyedia Kontak.

Membuat kueri entity

Karena tabel Penyedia Kontak diatur dalam hierarki, sering kali berguna untuk mengambil baris dan semua baris "turunan" yang ditautkan ke baris tersebut. Misalnya, untuk menampilkan semua informasi seseorang, Anda mungkin ingin mengambil semua baris ContactsContract.RawContacts untuk satu baris ContactsContract.Contacts, atau semua baris ContactsContract.CommonDataKinds.Email untuk satu baris ContactsContract.RawContacts. Untuk memfasilitasi hal ini, Penyedia Kontak menawarkan konstruksi entity, yang bertindak seperti gabungan database antar- tabel.

Entity seperti tabel yang terdiri dari kolom yang dipilih dari tabel induk dan tabel turunannya. Saat mengkueri entity, Anda memberikan proyeksi dan kriteria penelusuran berdasarkan kolom yang tersedia dari entity. Hasilnya adalah Cursor yang berisi satu baris untuk setiap baris tabel turunan yang diambil. Misalnya, jika Anda mengkueri ContactsContract.Contacts.Entity untuk nama kontak dan semua baris ContactsContract.CommonDataKinds.Email untuk semua kontak mentah untuk nama tersebut, Anda akan mendapatkan kembali Cursor yang berisi satu baris untuk setiap baris ContactsContract.CommonDataKinds.Email.

Entity menyederhanakan kueri. Dengan menggunakan entity, Anda dapat mengambil semua data kontak untuk kontak atau kontak mentah sekaligus, tanpa harus mengkueri tabel induk terlebih dahulu untuk mendapatkan ID, lalu mengkueri tabel turunan dengan ID tersebut. Selain itu, Penyedia Kontak memproses kueri terhadap entity dalam satu transaksi, yang memastikan bahwa data yang diambil konsisten secara internal.

Catatan: Entity biasanya tidak berisi semua kolom tabel induk dan turunan. Jika Anda mencoba menggunakan nama kolom yang tidak ada dalam daftar konstanta nama kolom untuk entity, Anda akan mendapatkan Exception.

Cuplikan berikut menunjukkan cara mengambil semua baris kontak mentah untuk sebuah kontak. Cuplikan ini adalah bagian dari aplikasi yang lebih besar yang memiliki dua aktivitas, "utama" dan "detail". Aktivitas utama menampilkan daftar baris kontak; saat pengguna memilih salah satunya, aktivitas akan mengirimkan ID-nya ke aktivitas detail. Aktivitas detail menggunakan ContactsContract.Contacts.Entity untuk menampilkan semua baris data dari semua kontak mentah yang terkait dengan kontak yang dipilih.

Cuplikan ini diambil dari aktivitas "detail":

Kotlin

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY
    )

    // Initializes the loader identified by LOADER_ID.
    loaderManager.initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this        // The context of the activity
    )

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = SimpleCursorAdapter(
            this,                       // the context of the activity
            R.layout.detail_list_item,  // the view item containing the detail widgets
            mCursor,                    // the backing cursor
            fromColumns,               // the columns in the cursor that provide the data
            toViews,                   // the views in the view item that display the data
            0)                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.adapter = cursorAdapter
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    val projection: Array<String> = arrayOf(
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
    )

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC"

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return CursorLoader(
            applicationContext, // The activity's context
            contactUri,        // The entity content URI for a single contact
            projection,         // The columns to retrieve
            null,               // Retrieve all the raw contacts and their data rows.
            null,               //
            sortOrder           // Sort by the raw contact ID.
    )
}

Java

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);

    // Initializes the loader identified by LOADER_ID.
    getLoaderManager().initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this);      // The context of the activity

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = new SimpleCursorAdapter(
            this,                        // the context of the activity
            R.layout.detail_list_item,   // the view item containing the detail widgets
            mCursor,                     // the backing cursor
            fromColumns,                // the columns in the cursor that provide the data
            toViews,                    // the views in the view item that display the data
            0);                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.setAdapter(cursorAdapter);
...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    String[] projection =
        {
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
        };

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    String sortOrder =
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
            " ASC";

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return new CursorLoader(
            getApplicationContext(),  // The activity's context
            contactUri,              // The entity content URI for a single contact
            projection,               // The columns to retrieve
            null,                     // Retrieve all the raw contacts and their data rows.
            null,                     //
            sortOrder);               // Sort by the raw contact ID.
}

Setelah pemuatan selesai, LoaderManager akan memanggil callback ke onLoadFinished(). Salah satu argumen yang masuk ke metode ini adalah Cursor dengan hasil kueri. Di aplikasi Anda sendiri, Anda bisa mendapatkan data dari Cursor ini untuk menampilkannya atau menggunakannya lebih lanjut.

Modifikasi batch

Jika memungkinkan, Anda harus menyisipkan, memperbarui, dan menghapus data di Penyedia Kontak dalam "mode batch", dengan membuat ArrayList dari objek ContentProviderOperation dan memanggil applyBatch(). Karena Penyedia Kontak melakukan semua operasi dalam applyBatch() dalam satu transaksi, modifikasi Anda tidak akan pernah membuat repositori kontak dalam status tidak konsisten. Modifikasi batch juga memfasilitasi penyisipan kontak mentah dan data detailnya secara bersamaan.

Catatan: Untuk mengubah satu kontak mentah, pertimbangkan untuk mengirim intent ke aplikasi kontak perangkat, bukan menangani perubahan di aplikasi Anda. Tindakan ini dijelaskan secara lebih mendetail di bagian Pengambilan dan perubahan dengan intent.

Poin hasil

Modifikasi batch yang berisi banyak operasi dapat memblokir proses lain, sehingga menghasilkan pengalaman pengguna yang buruk secara keseluruhan. Untuk mengatur semua modifikasi yang ingin Anda lakukan dalam daftar terpisah sesedikit mungkin, dan sekaligus mencegahnya memblokir sistem, Anda harus menetapkan poin hasil untuk satu atau beberapa operasi. Poin hasil adalah objek ContentProviderOperation yang memiliki nilai isYieldAllowed() yang ditetapkan ke true. Saat Penyedia Kontak menemukan poin hasil, penyedia akan menjeda pekerjaannya untuk memungkinkan proses lain berjalan dan menutup transaksi saat ini. Saat penyedia dimulai lagi, penyedia akan melanjutkan dengan operasi berikutnya di ArrayList dan memulai transaksi baru.

Poin hasil menghasilkan lebih dari satu transaksi per panggilan ke applyBatch(). Oleh karena itu, Anda harus menetapkan poin hasil untuk operasi terakhir untuk sekumpulan baris terkait. Misalnya, Anda harus menetapkan poin hasil untuk operasi terakhir dalam kumpulan yang menambahkan baris kontak mentah dan baris data terkaitnya, atau operasi terakhir untuk kumpulan baris yang terkait dengan satu kontak.

Poin hasil juga merupakan unit operasi atomik. Semua akses di antara dua poin hasil akan berhasil atau gagal sebagai satu unit. Jika Anda tidak menetapkan poin hasil apa pun, operasi atomik terkecil adalah seluruh batch operasi. Jika menggunakan poin hasil, Anda mencegah operasi menurunkan performa sistem, sekaligus memastikan bahwa subset operasi bersifat atomik.

Referensi balik modifikasi

Saat menyisipkan baris kontak mentah baru dan baris data terkaitnya sebagai kumpulan objek ContentProviderOperation, Anda harus menautkan baris data ke baris kontak mentah dengan menyisipkan nilai _ID kontak mentah sebagai nilai RAW_CONTACT_ID. Namun, nilai ini tidak tersedia saat Anda membuat ContentProviderOperation untuk baris data, karena Anda belum menerapkan ContentProviderOperation untuk baris kontak mentah. Untuk mengatasi hal ini, class ContentProviderOperation.Builder memiliki metode withValueBackReference(). Metode ini memungkinkan Anda menyisipkan atau mengubah kolom dengan hasil operasi sebelumnya.

Metode withValueBackReference() memiliki dua argumen:

key
Kunci dari pasangan nilai kunci. Nilai argumen ini harus berupa nama kolom dalam tabel yang Anda ubah.
previousResult
Indeks berbasis 0 dari nilai dalam array objek ContentProviderResult dari applyBatch(). Saat operasi batch diterapkan, hasil setiap operasi disimpan dalam array hasil perantara. Nilai previousResult adalah indeks dari salah satu hasil ini, yang diambil dan disimpan dengan nilai key. Hal ini memungkinkan Anda menyisipkan data kontak mentah baru dan mendapatkan kembali nilai _ID-nya, lalu membuat "referensi balik" ke nilai tersebut saat Anda menambahkan baris ContactsContract.Data.

Seluruh array hasil dibuat saat Anda pertama kali memanggil applyBatch(), dengan ukuran yang sama dengan ukuran ArrayList dari objek ContentProviderOperation yang Anda berikan. Namun, semua elemen dalam array hasil ditetapkan ke null, dan jika Anda mencoba melakukan referensi balik ke hasil untuk operasi yang belum diterapkan, withValueBackReference() akan menampilkan Exception.

Cuplikan berikut menunjukkan cara menyisipkan kontak mentah dan data baru dalam batch. Mereka menyertakan kode yang menetapkan poin hasil dan menggunakan referensi balik.

Cuplikan pertama mengambil data kontak dari UI. Pada tahap ini, pengguna telah memilih akun yang akan ditambahi kontak mentah baru.

Kotlin

// Creates a contact entry from the current UI values, using the currently-selected account.
private fun createContactEntry() {
    /*
     * Gets values from the UI
     */
    val name = contactNameEditText.text.toString()
    val phone = contactPhoneEditText.text.toString()
    val email = contactEmailEditText.text.toString()

    val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition]

    val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]

Java

// Creates a contact entry from the current UI values, using the currently-selected account.
protected void createContactEntry() {
    /*
     * Gets values from the UI
     */
    String name = contactNameEditText.getText().toString();
    String phone = contactPhoneEditText.getText().toString();
    String email = contactEmailEditText.getText().toString();

    int phoneType = contactPhoneTypes.get(
            contactPhoneTypeSpinner.getSelectedItemPosition());

    int emailType = contactEmailTypes.get(
            contactEmailTypeSpinner.getSelectedItemPosition());

Cuplikan berikutnya membuat operasi untuk menyisipkan baris kontak mentah ke dalam tabel ContactsContract.RawContacts:

Kotlin

    /*
     * Prepares the batch operation for inserting a new raw contact and its data. Even if
     * the Contacts Provider does not have any data for this person, you can't add a Contact,
     * only a raw contact. The Contacts Provider will then add a Contact automatically.
     */

    // Creates a new array of ContentProviderOperation objects.
    val ops = arrayListOf<ContentProviderOperation>()

    /*
     * Creates a new raw contact with its account type (server type) and account name
     * (user's account). Remember that the display name is not stored in this row, but in a
     * StructuredName data row. No other data is required.
     */
    var op: ContentProviderOperation.Builder =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Java

    /*
     * Prepares the batch operation for inserting a new raw contact and its data. Even if
     * the Contacts Provider does not have any data for this person, you can't add a Contact,
     * only a raw contact. The Contacts Provider will then add a Contact automatically.
     */

     // Creates a new array of ContentProviderOperation objects.
    ArrayList<ContentProviderOperation> ops =
            new ArrayList<ContentProviderOperation>();

    /*
     * Creates a new raw contact with its account type (server type) and account name
     * (user's account). Remember that the display name is not stored in this row, but in a
     * StructuredName data row. No other data is required.
     */
    ContentProviderOperation.Builder op =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType())
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

Selanjutnya, kode akan membuat baris data untuk baris nama tampilan, telepon, dan email.

Setiap objek builder operasi menggunakan withValueBackReference() untuk mendapatkan RAW_CONTACT_ID. Referensi mengarah kembali ke objek ContentProviderResult dari operasi pertama, yang menambahkan baris kontak mentah dan menampilkan nilai _ID barunya. Hasilnya, setiap baris data otomatis ditautkan oleh RAW_CONTACT_ID-nya ke baris ContactsContract.RawContacts baru yang sesuai.

Objek ContentProviderOperation.Builder yang menambahkan baris email ditandai dengan withYieldAllowed(), yang menetapkan poin hasil:

Kotlin

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

    // Inserts the specified phone number and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

    // Inserts the specified email and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Java

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified phone number and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified email and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

Cuplikan terakhir menunjukkan panggilan ke applyBatch() yang menyisipkan kontak mentah dan baris data baru.

Kotlin

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})")
    Log.d(TAG, "Creating contact: $name")

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
    } catch (e: Exception) {
        // Display a warning
        val txt: String = getString(R.string.contactCreationFailure)
        Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show()

        // Log exception
        Log.e(TAG, "Exception encountered while inserting contact: $e")
    }
}

Java

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" +
            selectedAccount.getType() + ")");
    Log.d(TAG,"Creating contact: " + name);

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {

            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
    } catch (Exception e) {

            // Display a warning
            Context ctx = getApplicationContext();

            CharSequence txt = getString(R.string.contactCreationFailure);
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(ctx, txt, duration);
            toast.show();

            // Log exception
            Log.e(TAG, "Exception encountered while inserting contact: " + e);
    }
}

Operasi batch juga memungkinkan Anda menerapkan kontrol konkurensi optimis, yaitu metode penerapan transaksi modifikasi tanpa harus mengunci repositori yang mendasarinya. Untuk menggunakan metode ini, Anda menerapkan transaksi, lalu memeriksa modifikasi lain yang mungkin telah dilakukan pada saat yang sama. Jika Anda menemukan bahwa modifikasi yang tidak konsisten telah terjadi, Anda me-roll back transaksi dan mencobanya lagi.

Kontrol konkurensi optimis berguna untuk perangkat seluler, yang hanya memiliki satu pengguna pada satu waktu, dan akses simultan ke repositori data jarang terjadi. Karena penguncian tidak digunakan, tidak ada waktu yang terbuang untuk menyetel kunci atau menunggu transaksi lain melepaskan kuncinya.

Untuk menggunakan kontrol konkurensi optimis saat mengupdate satu baris ContactsContract.RawContacts, ikuti langkah-langkah berikut:

  1. Ambil kolom VERSION kontak mentah beserta data lain yang Anda ambil.
  2. Buat objek ContentProviderOperation.Builder yang sesuai untuk menerapkan batasan, menggunakan metode newAssertQuery(Uri). Untuk URI konten, gunakan RawContacts.CONTENT_URI dengan _ID kontak mentah yang ditambahkan ke dalamnya.
  3. Untuk objek ContentProviderOperation.Builder, panggil withValue() untuk membandingkan kolom VERSION dengan nomor versi yang baru saja Anda ambil.
  4. Untuk ContentProviderOperation.Builder yang sama, panggil withExpectedCount() untuk memastikan bahwa hanya satu baris yang diuji oleh pernyataan ini.
  5. Panggil build() untuk membuat objek ContentProviderOperation, lalu tambahkan objek ini sebagai objek pertama dalam ArrayList yang Anda teruskan ke applyBatch().
  6. Terapkan transaksi batch.

Jika baris kontak mentah diperbarui oleh operasi lain antara waktu Anda membaca baris dan waktu Anda mencoba mengubahnya, "assert" ContentProviderOperation akan gagal, dan seluruh batch operasi akan dibatalkan. Kemudian, Anda dapat memilih untuk mencoba ulang batch atau melakukan tindakan lain.

Cuplikan berikut menunjukkan cara membuat ContentProviderOperation "assert" setelah mengkueri satu kontak mentah menggunakan CursorLoader:

Kotlin

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID))
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION))
}

...

// Sets up a Uri for the assert operation
val rawContactUri: Uri = ContentUris.withAppendedId(
        ContactsContract.RawContacts.CONTENT_URI,
        rawContactID
)

// Creates a builder for the assert operation
val assertOp: ContentProviderOperation.Builder =
        ContentProviderOperation.newAssertQuery(rawContactUri).apply {
            // Adds the assertions to the assert operation: checks the version
            withValue(SyncColumns.VERSION, mVersion)

            // and count of rows tested
            withExpectedCount(1)
        }

// Creates an ArrayList to hold the ContentProviderOperation objects
val ops = arrayListOf<ContentProviderOperation>()

ops.add(assertOp.build())

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try {
    val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops)
} catch (e: OperationApplicationException) {
    // Actions you want to take if the assert operation fails go here
}

Java

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {

    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
}

...

// Sets up a Uri for the assert operation
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactID);

// Creates a builder for the assert operation
ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(rawContactUri);

// Adds the assertions to the assert operation: checks the version and count of rows tested
assertOp.withValue(SyncColumns.VERSION, mVersion);
assertOp.withExpectedCount(1);

// Creates an ArrayList to hold the ContentProviderOperation objects
ArrayList ops = new ArrayList<ContentProviderOperation>;

ops.add(assertOp.build());

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try
    {
        ContentProviderResult[] results =
                getContentResolver().applyBatch(AUTHORITY, ops);

    } catch (OperationApplicationException e) {

        // Actions you want to take if the assert operation fails go here
    }

Pengambilan dan modifikasi dengan intent

Mengirim intent ke aplikasi kontak perangkat memungkinkan Anda mengakses Penyedia Kontak secara tidak langsung. Intent memulai UI aplikasi kontak perangkat, tempat pengguna dapat melakukan pekerjaan terkait kontak. Dengan jenis akses ini, pengguna dapat:

  • Memilih kontak dari daftar dan mengembalikannya ke aplikasi Anda untuk pekerjaan lebih lanjut.
  • Mengedit data kontak yang ada.
  • Menyisipkan kontak mentah baru untuk akun mereka.
  • Menghapus data kontak.

Jika pengguna menyisipkan atau memperbarui data, Anda dapat mengumpulkan data terlebih dahulu dan mengirimkannya sebagai bagian dari intent.

Saat menggunakan intent untuk mengakses Penyedia Kontak melalui aplikasi kontak perangkat, Anda tidak perlu menulis UI atau kode sendiri untuk mengakses penyedia. Anda juga tidak perlu meminta izin untuk membaca atau menulis ke penyedia. Aplikasi kontak perangkat dapat mendelegasikan izin baca untuk kontak kepada Anda, dan karena Anda melakukan modifikasi pada penyedia melalui aplikasi lain, Anda tidak perlu memiliki izin tulis.

Proses umum pengiriman intent untuk mengakses penyedia dijelaskan secara mendetail dalam panduan Dasar-dasar Penyedia Konten di bagian "Akses data melalui intent". Tindakan, jenis MIME, dan nilai data yang Anda gunakan untuk tugas yang tersedia diringkas dalam Tabel 4, sedangkan nilai tambahan yang dapat Anda gunakan dengan putExtra() tercantum dalam dokumentasi referensi untuk ContactsContract.Intents.Insert:

Tabel 4. Intent Penyedia Kontak.

Tugas Tindakan Data Jenis MIME Catatan
Memilih kontak dari daftar ACTION_PICK Salah satu dari: Tidak digunakan Menampilkan daftar kontak mentah atau daftar data dari kontak mentah, bergantung pada jenis URI konten yang Anda berikan.

Panggil startActivityForResult(), yang menampilkan URI konten baris yang dipilih. Bentuk URI adalah URI konten tabel dengan LOOKUP_ID baris yang ditambahkan ke dalamnya. Aplikasi kontak perangkat mendelegasikan izin baca dan tulis ke URI konten ini selama masa aktif aktivitas Anda. Lihat panduan Dasar-dasar Penyedia Konten untuk mengetahui detail selengkapnya.

Menyisipkan kontak mentah baru Insert.ACTION T/A RawContacts.CONTENT_TYPE, jenis MIME untuk kumpulan kontak mentah. Menampilkan layar Tambahkan Kontak aplikasi kontak perangkat. Nilai tambahan yang Anda tambahkan ke intent akan ditampilkan. Jika dikirim dengan startActivityForResult(), URI konten kontak mentah yang baru ditambahkan akan diteruskan kembali ke metode callback onActivityResult() aktivitas Anda dalam argumen Intent, di kolom "data". Untuk mendapatkan nilai, panggil getData().
Mengedit kontak ACTION_EDIT CONTENT_LOOKUP_URI untuk kontak. Aktivitas editor akan memungkinkan pengguna mengedit data apa pun yang terkait dengan kontak ini. Contacts.CONTENT_ITEM_TYPE, satu kontak. Menampilkan layar Edit Kontak di aplikasi kontak. Nilai tambahan yang Anda tambahkan ke intent akan ditampilkan. Saat pengguna mengklik Selesai untuk menyimpan editan, aktivitas Anda akan kembali ke latar depan.
Menampilkan pemilih yang juga dapat menambahkan data. ACTION_INSERT_OR_EDIT T/A CONTENT_ITEM_TYPE Intent ini selalu menampilkan layar pemilih aplikasi kontak. Pengguna dapat memilih kontak untuk diedit, atau menambahkan kontak baru. Layar edit atau tambahkan akan muncul, bergantung pada pilihan pengguna, dan data tambahan yang Anda teruskan dalam intent akan ditampilkan. Jika aplikasi Anda menampilkan data kontak seperti email atau nomor telepon, gunakan intent ini untuk mengizinkan pengguna menambahkan data ke kontak yang ada. kontak,

Catatan: Tidak perlu mengirim nilai nama dalam tambahan intent ini, karena pengguna selalu memilih nama yang ada atau menambahkan nama baru. Selain itu, jika Anda mengirim nama, dan pengguna memilih untuk melakukan pengeditan, aplikasi kontak akan menampilkan nama yang Anda kirim, yang menimpa nilai sebelumnya. Jika pengguna tidak memperhatikan hal ini dan menyimpan hasil edit, nilai lama akan hilang.

Aplikasi kontak perangkat tidak mengizinkan Anda menghapus kontak mentah atau datanya dengan intent. Sebagai gantinya, untuk menghapus kontak mentah, gunakan ContentResolver.delete() atau ContentProviderOperation.newDelete().

Cuplikan berikut menunjukkan cara membuat dan mengirim intent yang menyisipkan kontak mentah dan data baru:

Kotlin

// Gets values from the UI
val name = contactNameEditText.text.toString()
val phone = contactPhoneEditText.text.toString()
val email = contactEmailEditText.text.toString()

val company = companyName.text.toString()
val jobtitle = jobTitle.text.toString()

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
val contactData = arrayListOf<ContentValues>()

/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
val rawContactRow = ContentValues().apply {
    // Adds the account type and name to the row
    put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type)
    put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name)
}

// Adds the row to the array
contactData.add(rawContactRow)

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
val phoneRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

    // Adds the phone number and its type to the row
    put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
}

// Adds the row to the array
contactData.add(phoneRow)

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
val emailRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

    // Adds the email address and its type to the row
    put(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
}

// Adds the row to the array
contactData.add(emailRow)

// Creates a new intent for sending to the device's contacts application
val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply {
    // Sets the MIME type to the one expected by the insertion activity
    type = ContactsContract.RawContacts.CONTENT_TYPE

    // Sets the new contact name
    putExtra(ContactsContract.Intents.Insert.NAME, name)

    // Sets the new company and job title
    putExtra(ContactsContract.Intents.Insert.COMPANY, company)
    putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle)

    /*
    * Adds the array to the intent's extras. It must be a parcelable object in order to
    * travel between processes. The device's contacts app expects its key to be
    * Intents.Insert.DATA
    */
    putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData)
}

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent)

Java

// Gets values from the UI
String name = contactNameEditText.getText().toString();
String phone = contactPhoneEditText.getText().toString();
String email = contactEmailEditText.getText().toString();

String company = companyName.getText().toString();
String jobtitle = jobTitle.getText().toString();

// Creates a new intent for sending to the device's contacts application
Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);

// Sets the MIME type to the one expected by the insertion activity
insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);

// Sets the new contact name
insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);

// Sets the new company and job title
insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();


/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
ContentValues rawContactRow = new ContentValues();

// Adds the account type and name to the row
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

// Adds the row to the array
contactData.add(rawContactRow);

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
ContentValues phoneRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
phoneRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
);

// Adds the phone number and its type to the row
phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);

// Adds the row to the array
contactData.add(phoneRow);

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
ContentValues emailRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
emailRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
);

// Adds the email address and its type to the row
emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);

// Adds the row to the array
contactData.add(emailRow);

/*
 * Adds the array to the intent's extras. It must be a parcelable object in order to
 * travel between processes. The device's contacts app expects its key to be
 * Intents.Insert.DATA
 */
insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent);

Integritas data

Karena repositori kontak berisi data penting dan sensitif yang diharapkan pengguna benar dan terbaru, Penyedia Kontak memiliki aturan yang jelas untuk integritas data. Anda bertanggung jawab untuk mematuhi aturan ini saat mengubah data kontak. Aturan pentingnya tercantum di sini:

Selalu tambahkan baris ContactsContract.CommonDataKinds.StructuredName untuk setiap baris ContactsContract.RawContacts yang Anda tambahkan.
Baris ContactsContract.RawContacts tanpa baris ContactsContract.CommonDataKinds.StructuredName di tabel ContactsContract.Data dapat menyebabkan masalah selama penggabungan.
Selalu tautkan baris ContactsContract.Data baru ke baris ContactsContract.RawContacts induknya.
Baris ContactsContract.Data yang tidak ditautkan ke ContactsContract.RawContacts tidak akan terlihat di aplikasi kontak perangkat, dan dapat menyebabkan masalah pada adaptor sinkronisasi.
Ubah data hanya untuk kontak mentah yang Anda miliki.
Ingat bahwa Penyedia Kontak biasanya mengelola data dari beberapa jenis akun/layanan online yang berbeda. Anda harus memastikan bahwa aplikasi Anda hanya mengubah atau menghapus data untuk baris milik Anda, dan hanya menyisipkan data dengan jenis dan nama akun yang Anda kontrol.
Selalu gunakan konstanta yang ditentukan dalam ContactsContract dan subclass-nya untuk otoritas, URI konten, jalur URI, nama kolom, jenis MIME, dan nilai TYPE.
Menggunakan konstanta ini akan membantu Anda menghindari error. Anda juga akan diberi tahu dengan peringatan compiler jika ada konstanta yang tidak digunakan lagi.

Baris data kustom

Dengan membuat dan menggunakan jenis MIME kustom Anda sendiri, Anda dapat menyisipkan, mengedit, menghapus, dan mengambil baris data Anda sendiri di tabel ContactsContract.Data. Baris Anda dibatasi untuk menggunakan kolom yang ditentukan dalam ContactsContract.DataColumns, meskipun Anda dapat memetakan nama kolom khusus jenis Anda sendiri ke nama kolom default. Dalam aplikasi kontak perangkat, data untuk baris Anda ditampilkan, tetapi tidak dapat diedit atau dihapus, dan pengguna tidak dapat menambahkan data tambahan. Agar pengguna dapat mengubah baris data kustom, Anda harus menyediakan aktivitas editor di aplikasi Anda sendiri.

Untuk menampilkan data kustom, sediakan file contacts.xml yang berisi elemen <ContactsAccountType> dan satu atau beberapa elemen turunan <ContactsDataKind>. Hal ini dijelaskan secara lebih mendetail di bagian <ContactsDataKind> element.

Untuk mempelajari lebih lanjut jenis MIME kustom, baca panduan Membuat Penyedia Konten.

Adaptor sinkronisasi Penyedia Kontak

Penyedia Kontak dirancang khusus untuk menangani sinkronisasi data kontak antara perangkat dan layanan online. Hal ini memungkinkan pengguna mendownload data yang ada ke perangkat baru dan mengupload data yang ada ke akun baru. Sinkronisasi juga memastikan bahwa pengguna memiliki data terbaru, terlepas dari sumber penambahan dan perubahan. Keuntungan lain dari sinkronisasi adalah membuat data kontak tersedia bahkan saat perangkat tidak terhubung ke jaringan.

Meskipun Anda dapat menerapkan sinkronisasi dengan berbagai cara, sistem Android menyediakan framework sinkronisasi plug-in yang mengotomatiskan tugas-tugas berikut:

  • Memeriksa ketersediaan jaringan.
  • Menjadwalkan dan menjalankan sinkronisasi, berdasarkan preferensi pengguna.
  • Memulai ulang sinkronisasi yang telah berhenti.

Untuk menggunakan framework ini, Anda menyediakan plugin adaptor sinkronisasi. Setiap adaptor sinkronisasi bersifat unik untuk layanan dan penyedia konten, tetapi dapat menangani beberapa nama akun untuk layanan yang sama. Framework ini juga memungkinkan beberapa adaptor sinkronisasi untuk layanan dan penyedia yang sama.

Class dan file adaptor sinkronisasi

Anda mengimplementasikan adaptor sinkronisasi sebagai subclass dari AbstractThreadedSyncAdapter dan menginstalnya sebagai bagian dari aplikasi Android. Sistem mempelajari adaptor sinkronisasi dari elemen dalam manifes aplikasi Anda, dan dari file XML khusus yang ditunjuk oleh manifes. File XML menentukan jenis akun untuk layanan online dan otoritas untuk penyedia konten, yang bersama-sama mengidentifikasi adaptor secara unik. Adaptor sinkronisasi tidak akan aktif hingga pengguna menambahkan akun untuk jenis akun adaptor sinkronisasi dan mengaktifkan sinkronisasi untuk penyedia konten yang disinkronkan dengan adaptor sinkronisasi. Pada tahap tersebut, sistem akan mulai mengelola adaptor, memanggilnya seperlunya untuk menyinkronkan antara penyedia konten dan server.

Catatan: Menggunakan jenis akun sebagai bagian dari identifikasi adaptor sinkronisasi memungkinkan sistem mendeteksi dan mengelompokkan adaptor sinkronisasi yang mengakses berbagai layanan dari organisasi yang sama. Misalnya, adaptor sinkronisasi untuk layanan online Google semuanya memiliki jenis akun com.google yang sama. Saat pengguna menambahkan Akun Google ke perangkat mereka, semua adaptor sinkronisasi yang diinstal untuk layanan Google akan dicantumkan bersama; setiap adaptor sinkronisasi yang tercantum akan disinkronkan dengan penyedia konten yang berbeda di perangkat.

Karena sebagian besar layanan mengharuskan pengguna memverifikasi identitas mereka sebelum mengakses data, sistem Android menawarkan framework autentikasi yang mirip dengan, dan sering digunakan bersama dengan, framework adaptor sinkronisasi. Framework autentikasi menggunakan pengautentikasi plug-in yang merupakan subclass dari AbstractAccountAuthenticator. Pengautentikasi memverifikasi identitas pengguna dalam langkah-langkah berikut:

  1. Mengumpulkan nama, sandi, atau informasi serupa milik pengguna (kredensial pengguna).
  2. Mengirim kredensial ke layanan
  3. Memeriksa balasan layanan.

Jika layanan menerima kredensial, pengautentikasi dapat menyimpan kredensial untuk digunakan nanti. Karena framework pengautentikasi plug-in, AccountManager dapat memberikan akses ke token autentikasi apa pun yang didukung dan dipilih oleh pengautentikasi untuk diekspos, seperti token autentikasi OAuth2.

Meskipun autentikasi tidak diperlukan, sebagian besar layanan kontak menggunakannya. Namun, Anda tidak harus menggunakan framework autentikasi Android untuk melakukan autentikasi.

Implementasi adaptor sinkronisasi

Untuk mengimplementasikan adaptor sinkronisasi untuk Penyedia Kontak, Anda harus memulai dengan membuat aplikasi Android yang berisi hal berikut:

Komponen Service yang merespons permintaan dari sistem untuk mengikat ke adaptor sinkronisasi.
Saat sistem ingin menjalankan sinkronisasi, sistem akan memanggil metode onBind() layanan untuk mendapatkan IBinder untuk adaptor sinkronisasi. Hal ini memungkinkan sistem melakukan panggilan lintas proses ke metode adaptor.
Adaptor sinkronisasi yang sebenarnya, yang diimplementasikan sebagai subclass konkret dari AbstractThreadedSyncAdapter.
Class ini melakukan pekerjaan mendownload data dari server, mengupload data dari perangkat, dan menyelesaikan konflik. Pekerjaan utama adaptor dilakukan dalam metode onPerformSync(). Class ini harus dibuat instance-nya sebagai singleton.
Subclass dari Application.
Class ini bertindak sebagai factory untuk singleton adaptor sinkronisasi. Gunakan metode onCreate() untuk membuat instance adaptor sinkronisasi, dan berikan metode "pengambil" statis untuk menampilkan singleton ke metode onBind() dari layanan adaptor sinkronisasi.
Opsional: Komponen Service yang merespons permintaan dari sistem untuk autentikasi pengguna.
AccountManager memulai layanan ini untuk memulai proses autentikasi. Metode onCreate() layanan membuat instance objek pengautentikasi. Saat sistem ingin mengautentikasi akun pengguna untuk adaptor sinkronisasi aplikasi, sistem akan memanggil metode onBind() layanan untuk mendapatkan IBinder untuk pengautentikasi. Hal ini memungkinkan sistem melakukan panggilan lintas proses ke metode pengautentikasi.
Opsional: Subclass konkret dari AbstractAccountAuthenticator yang menangani permintaan autentikasi.
Class ini menyediakan metode yang dipanggil AccountManager untuk mengautentikasi kredensial pengguna dengan server. Detail proses autentikasi sangat bervariasi, berdasarkan teknologi server yang digunakan. Anda harus membaca dokumentasi software server untuk mempelajari autentikasi lebih lanjut.
File XML yang menentukan adaptor sinkronisasi dan pengautentikasi ke sistem.
Komponen layanan adaptor sinkronisasi dan pengautentikasi yang dijelaskan sebelumnya ditentukan dalam elemen <service> dalam manifes aplikasi. Elemen ini berisi <meta-data> elemen turunan yang menyediakan data spesifik ke sistem:
  • Elemen <meta-data> untuk layanan adaptor sinkronisasi mengarah ke file XML res/xml/syncadapter.xml. Pada gilirannya, file ini menentukan URI untuk layanan web yang akan disinkronkan dengan Penyedia Kontak, dan jenis akun untuk layanan web.
  • Opsional: Elemen <meta-data> untuk pengautentikasi mengarah ke file XML res/xml/authenticator.xml. Pada gilirannya, file ini menentukan jenis akun yang didukung pengautentikasi ini, serta resource UI yang muncul selama proses autentikasi. Jenis akun yang ditentukan dalam elemen ini harus sama dengan jenis akun yang ditentukan untuk adaptor sinkronisasi.

Data streaming sosial

Tabel android.provider.ContactsContract.StreamItems dan android.provider.ContactsContract.StreamItemPhotos mengelola data yang masuk dari jaringan sosial. Anda dapat menulis adaptor sinkronisasi yang menambahkan data streaming dari jaringan Anda sendiri ke tabel ini, atau Anda dapat membaca data streaming dari tabel ini dan menampilkannya di aplikasi Anda sendiri, atau keduanya. Dengan fitur ini, layanan dan aplikasi jejaring sosial Anda dapat diintegrasikan ke dalam pengalaman jejaring sosial Android.

Teks aliran sosial

Item streaming selalu dikaitkan dengan kontak mentah. android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID ditautkan ke nilai _ID untuk kontak mentah. Jenis akun dan nama akun kontak mentah juga disimpan di baris item streaming.

Simpan data dari streaming Anda di kolom berikut:

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
Wajib. Jenis akun pengguna untuk kontak mentah yang terkait dengan item aliran ini. Jangan lupa untuk menetapkan nilai ini saat Anda menyisipkan item streaming.
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
Wajib. Nama akun pengguna untuk kontak mentah yang terkait dengan item stream ini. Jangan lupa untuk menetapkan nilai ini saat Anda menyisipkan item streaming.
Kolom ID
Wajib. Anda harus menyisipkan kolom ID berikut saat menyisipkan item streaming:
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: Nilai android.provider.BaseColumns#_ID dari kontak yang terkait dengan item aliran ini.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: Nilai android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY dari kontak yang terkait dengan item aliran ini.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: Nilai android.provider.BaseColumns#_ID dari kontak mentah yang terkait dengan item aliran ini.
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
Opsional. Menyimpan informasi ringkasan yang dapat Anda tampilkan di awal item forum.
android.provider.ContactsContract.StreamItemsColumns#TEXT
Teks item streaming, baik konten yang diposting oleh sumber item, maupun deskripsi beberapa tindakan yang menghasilkan item streaming. Kolom ini dapat berisi format dan gambar resource tersemat apa pun yang dapat dirender oleh fromHtml(). Penyedia dapat memotong atau menyingkat konten yang panjang, tetapi akan mencoba menghindari tag yang rusak.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
String teks yang berisi waktu item streaming disisipkan atau diperbarui, dalam bentuk milidetik sejak epoch. Aplikasi yang menyisipkan atau memperbarui item streaming bertanggung jawab untuk mengelola kolom ini; kolom ini tidak dikelola secara otomatis oleh Penyedia Kontak.

Untuk menampilkan informasi identitas pribadi untuk item streaming, gunakan android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL, dan android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE untuk menautkan ke resource dalam aplikasi Anda.

Tabel android.provider.ContactsContract.StreamItems juga berisi kolom android.provider.ContactsContract.StreamItemsColumns#SYNC1 hingga android.provider.ContactsContract.StreamItemsColumns#SYNC4 untuk penggunaan eksklusif adaptor sinkronisasi.

Foto streaming sosial

Tabel android.provider.ContactsContract.StreamItemPhotos menyimpan foto yang terkait dengan item streaming. Kolom android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID tabel menautkan ke nilai di kolom _ID tabel android.provider.ContactsContract.StreamItems. Referensi foto disimpan dalam tabel di kolom berikut:

kolom android.provider.ContactsContract.StreamItemPhotos#PHOTO (BLOB).
Representasi biner foto, yang diubah ukurannya oleh penyedia untuk penyimpanan dan tampilan. Kolom ini tersedia untuk kompatibilitas mundur dengan versi Penyedia Kontak sebelumnya yang menggunakannya untuk menyimpan foto. Namun, dalam versi saat ini, Anda tidak boleh menggunakan kolom ini untuk menyimpan foto. Sebagai gantinya, gunakan android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID atau android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (keduanya dijelaskan dalam poin berikut) untuk menyimpan foto dalam file. Kolom ini sekarang berisi thumbnail foto, yang tersedia untuk dibaca.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
ID numerik foto untuk kontak mentah. Tambahkan nilai ini ke konstanta DisplayPhoto.CONTENT_URI untuk mendapatkan URI konten yang mengarah ke satu file foto, lalu panggil openAssetFileDescriptor() untuk mendapatkan handle ke file foto.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
URI konten yang mengarah langsung ke file foto untuk foto yang diwakili oleh baris ini. Panggil openAssetFileDescriptor() dengan URI ini untuk mendapatkan handle ke file foto.

Menggunakan tabel streaming sosial

Tabel ini berfungsi sama seperti tabel utama lainnya di Penyedia Kontak, kecuali bahwa:

  • Tabel ini memerlukan izin akses tambahan. Untuk membaca dari penyedia tersebut, aplikasi Anda harus memiliki izin android.Manifest.permission#READ_SOCIAL_STREAM. Untuk mengubahnya, aplikasi Anda harus memiliki izin android.Manifest.permission#WRITE_SOCIAL_STREAM.
  • Untuk tabel android.provider.ContactsContract.StreamItems, jumlah baris yang disimpan untuk setiap kontak mentah dibatasi. Setelah batas ini tercapai, Penyedia Kontak akan menyediakan ruang untuk baris item streaming baru dengan otomatis menghapus baris yang memiliki android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP terlama. Untuk mendapatkan batas, kirim kueri ke URI konten android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Anda dapat membiarkan semua argumen selain URI konten ditetapkan ke null. Kueri menampilkan Cursor yang berisi satu baris, dengan satu kolom android.provider.ContactsContract.StreamItems#MAX_ITEMS.

Class android.provider.ContactsContract.StreamItems.StreamItemPhotos menentukan sub-tabel android.provider.ContactsContract.StreamItemPhotos yang berisi baris foto untuk satu item streaming.

Interaksi streaming sosial

Data streaming sosial yang dikelola oleh Penyedia Kontak, bersama dengan aplikasi kontak perangkat, menawarkan cara yang efektif untuk menghubungkan sistem jejaring sosial Anda dengan kontak yang ada. Tersedia fitur-fitur berikut:

  • Dengan menyinkronkan layanan jejaring sosial ke Penyedia Kontak menggunakan adaptor sinkronisasi, Anda dapat mengambil aktivitas terbaru untuk kontak pengguna dan menyimpannya di tabel android.provider.ContactsContract.StreamItems dan android.provider.ContactsContract.StreamItemPhotos untuk digunakan nanti.
  • Selain sinkronisasi reguler, Anda dapat memicu adaptor sinkronisasi untuk mengambil data tambahan saat pengguna memilih kontak untuk dilihat. Hal ini memungkinkan adaptor sinkronisasi Anda mengambil foto beresolusi tinggi dan item streaming terbaru untuk kontak.
  • Dengan mendaftarkan notifikasi ke aplikasi kontak perangkat dan Penyedia Kontak, Anda dapat menerima intent saat kontak dilihat, dan pada saat itu mengupdate status kontak dari layanan Anda. Pendekatan ini mungkin lebih cepat dan menggunakan lebih sedikit bandwidth daripada melakukan sinkronisasi penuh dengan adaptor sinkronisasi.
  • Pengguna dapat menambahkan kontak ke layanan jejaring sosial Anda saat melihat kontak di aplikasi kontak perangkat. Anda mengaktifkannya dengan fitur "undang kontak", yang Anda aktifkan dengan kombinasi aktivitas yang menambahkan kontak yang ada ke jaringan Anda, dan file XML yang menyediakan aplikasi kontak perangkat dan Penyedia Kontak dengan detail aplikasi Anda.

Sinkronisasi reguler item streaming dengan Penyedia Kontak sama dengan sinkronisasi lainnya. Untuk mempelajari sinkronisasi lebih lanjut, lihat bagian Adaptor sinkronisasi Penyedia Kontak. Mendaftarkan notifikasi dan mengundang kontak akan dibahas di dua bagian berikutnya.

Mendaftar untuk menangani tampilan jejaring sosial

Untuk mendaftarkan adaptor sinkronisasi agar menerima notifikasi saat pengguna melihat kontak yang dikelola oleh adaptor sinkronisasi Anda:

  1. Buat file bernama contacts.xml di direktori res/xml/ project Anda. Jika sudah memiliki file ini, Anda dapat melewati langkah ini.
  2. Dalam file ini, tambahkan elemen <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Jika elemen ini sudah ada, Anda dapat melewati langkah ini.
  3. Untuk mendaftarkan layanan yang diberi tahu saat pengguna membuka halaman detail kontak di aplikasi kontak perangkat, tambahkan atribut viewContactNotifyService="serviceclass" ke elemen, dengan serviceclass adalah nama class yang sepenuhnya memenuhi syarat dari layanan yang akan menerima intent dari aplikasi kontak perangkat. Untuk layanan pemberitahu, gunakan class yang memperluas IntentService, agar layanan dapat menerima intent. Data dalam intent yang masuk berisi URI konten dari kontak mentah yang diklik pengguna. Dari layanan notifikasi, Anda dapat mengikat dan kemudian memanggil adaptor sinkronisasi untuk memperbarui data kontak mentah.

Untuk mendaftarkan aktivitas yang akan dipanggil saat pengguna mengklik item atau foto di feed, atau keduanya:

  1. Buat file bernama contacts.xml di direktori res/xml/ project Anda. Jika sudah memiliki file ini, Anda dapat melewati langkah ini.
  2. Dalam file ini, tambahkan elemen <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Jika elemen ini sudah ada, Anda dapat melewati langkah ini.
  3. Untuk mendaftarkan salah satu aktivitas Anda guna menangani pengguna yang mengklik item streaming di aplikasi kontak perangkat, tambahkan atribut viewStreamItemActivity="activityclass" ke elemen, dengan activityclass adalah nama class yang sepenuhnya memenuhi syarat dari aktivitas yang akan menerima intent dari aplikasi kontak perangkat.
  4. Untuk mendaftarkan salah satu aktivitas Anda guna menangani pengguna yang mengklik foto streaming di aplikasi kontak perangkat, tambahkan atribut viewStreamItemPhotoActivity="activityclass" ke elemen, dengan activityclass adalah nama class yang sepenuhnya memenuhi syarat dari aktivitas yang akan menerima intent dari aplikasi kontak perangkat.

Elemen <ContactsAccountType> dijelaskan secara lebih mendetail di bagian Elemen<ContactsAccountType>.

Intent yang masuk berisi URI konten item atau foto yang diklik pengguna. Untuk memiliki aktivitas terpisah untuk item teks dan foto, gunakan kedua atribut dalam file yang sama.

Berinteraksi dengan layanan jejaring sosial Anda

Pengguna tidak perlu keluar dari aplikasi kontak perangkat untuk mengundang kontak ke situs jejaring sosial Anda. Sebagai gantinya, Anda dapat meminta aplikasi kontak perangkat mengirim intent untuk mengundang kontak ke salah satu aktivitas Anda. Untuk menyiapkannya:

  1. Buat file bernama contacts.xml di direktori res/xml/ project Anda. Jika sudah memiliki file ini, Anda dapat melewati langkah ini.
  2. Dalam file ini, tambahkan elemen <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Jika elemen ini sudah ada, Anda dapat melewati langkah ini.
  3. Tambahkan atribut berikut:
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    Nilai activityclass adalah nama class yang sepenuhnya memenuhi syarat dari aktivitas yang akan menerima intent. Nilai invite_action_label adalah string teks yang ditampilkan di menu Tambahkan Koneksi di aplikasi kontak perangkat.

Catatan: ContactsSource adalah nama tag yang tidak digunakan lagi untuk ContactsAccountType.

Referensi contacts.xml

File contacts.xml berisi elemen XML yang mengontrol interaksi adaptor sinkronisasi dan aplikasi Anda dengan aplikasi kontak dan Penyedia Kontak. Elemen-elemen ini dijelaskan di bagian berikut.

Elemen <ContactsAccountType>

Elemen <ContactsAccountType> mengontrol interaksi aplikasi Anda dengan aplikasi kontak. Fungsi ini memiliki sintaksis berikut:

<ContactsAccountType
        xmlns:android="http://schemas.android.com/apk/res/android"
        inviteContactActivity="activity_name"
        inviteContactActionLabel="invite_command_text"
        viewContactNotifyService="view_notify_service"
        viewGroupActivity="group_view_activity"
        viewGroupActionLabel="group_action_text"
        viewStreamItemActivity="viewstream_activity_name"
        viewStreamItemPhotoActivity="viewphotostream_activity_name">

terkandung dalam:

res/xml/contacts.xml

dapat berisi:

<ContactsDataKind>

Deskripsi:

Mendeklarasikan komponen Android dan label UI yang memungkinkan pengguna mengundang salah satu kontak mereka ke jejaring sosial, memberi tahu pengguna saat salah satu streaming jejaring sosial mereka diperbarui, dan sebagainya.

Perhatikan bahwa awalan atribut android: tidak diperlukan untuk atribut <ContactsAccountType>.

Atribut:

inviteContactActivity
Nama class yang sepenuhnya memenuhi syarat dari aktivitas di aplikasi Anda yang ingin Anda aktifkan saat pengguna memilih Tambahkan koneksi dari aplikasi kontak perangkat.
inviteContactActionLabel
String teks yang ditampilkan untuk aktivitas yang ditentukan di inviteContactActivity, di menu Tambahkan koneksi. Misalnya, Anda dapat menggunakan string "Ikuti di jaringan saya". Anda dapat menggunakan ID resource string untuk label ini.
viewContactNotifyService
Nama class yang sepenuhnya memenuhi syarat dari layanan di aplikasi Anda yang akan menerima notifikasi saat pengguna melihat kontak. Notifikasi ini dikirim oleh aplikasi kontak perangkat; notifikasi ini memungkinkan aplikasi Anda menunda operasi yang menggunakan banyak data hingga diperlukan. Misalnya, aplikasi Anda dapat merespons notifikasi ini dengan membaca dan menampilkan foto kontak beresolusi tinggi dan item feed sosial terbaru. Fitur ini dijelaskan secara lebih mendetail di bagian Interaksi streaming sosial.
viewGroupActivity
Nama class yang sepenuhnya memenuhi syarat dari aktivitas di aplikasi Anda yang dapat menampilkan informasi grup. Saat pengguna mengklik label grup di aplikasi kontak perangkat, UI untuk aktivitas ini akan ditampilkan.
viewGroupActionLabel
Label yang ditampilkan aplikasi kontak untuk kontrol UI yang memungkinkan pengguna melihat grup di aplikasi Anda.

ID resource string diizinkan untuk atribut ini.

viewStreamItemActivity
Nama class yang sepenuhnya memenuhi syarat dari aktivitas di aplikasi Anda yang diluncurkan oleh aplikasi kontak perangkat saat pengguna mengklik item streaming untuk kontak mentah.
viewStreamItemPhotoActivity
Nama class yang sepenuhnya memenuhi syarat dari aktivitas di aplikasi Anda yang diluncurkan oleh aplikasi kontak perangkat saat pengguna mengklik foto di item streaming untuk kontak mentah.

Elemen <ContactsDataKind>

Elemen <ContactsDataKind> mengontrol tampilan baris data kustom aplikasi Anda di UI aplikasi kontak. Fungsi ini memiliki sintaksis berikut:

<ContactsDataKind
        android:mimeType="MIMEtype"
        android:icon="icon_resources"
        android:summaryColumn="column_name"
        android:detailColumn="column_name">

terkandung dalam:

<ContactsAccountType>

Deskripsi:

Gunakan elemen ini agar aplikasi kontak menampilkan konten baris data kustom sebagai bagian dari detail kontak mentah. Setiap elemen turunan <ContactsDataKind> dari <ContactsAccountType> mewakili jenis baris data kustom yang ditambahkan adaptor sinkronisasi Anda ke tabel ContactsContract.Data. Tambahkan satu elemen <ContactsDataKind> untuk setiap jenis MIME kustom yang Anda gunakan. Anda tidak perlu menambahkan elemen jika memiliki baris data kustom yang datanya tidak ingin ditampilkan.

Atribut:

android:mimeType
Jenis MIME kustom yang telah Anda tentukan untuk salah satu jenis baris data kustom di tabel ContactsContract.Data. Misalnya, nilai vnd.android.cursor.item/vnd.example.locationstatus dapat berupa jenis MIME kustom untuk baris data yang mencatat lokasi terakhir kontak yang diketahui.
android:icon
Resource drawable Android yang ditampilkan aplikasi kontak di samping data Anda. Gunakan ini untuk menunjukkan kepada pengguna bahwa data berasal dari layanan Anda.
android:summaryColumn
Nama kolom untuk nilai pertama dari dua nilai yang diambil dari baris data. Nilai ditampilkan sebagai baris pertama entri untuk baris data ini. Baris pertama dimaksudkan untuk digunakan sebagai ringkasan data, tetapi bersifat opsional. Lihat juga android:detailColumn.
android:detailColumn
Nama kolom untuk nilai kedua dari dua nilai yang diambil dari baris data. Nilai ditampilkan sebagai baris kedua entri untuk baris data ini. Lihat juga android:summaryColumn.

Fitur Penyedia Kontak tambahan

Selain fitur utama yang dijelaskan di bagian sebelumnya, Penyedia Kontak menawarkan fitur berguna berikut untuk menangani data kontak:

  • Grup kontak
  • Fitur Foto

Grup kontak

Penyedia Kontak dapat secara opsional memberi label pada kumpulan kontak terkait dengan data grup. Jika server yang terkait dengan akun pengguna ingin mengelola grup, adaptor sinkronisasi untuk jenis akun tersebut harus mentransfer data grup antara Penyedia Kontak dan server. Saat pengguna menambahkan kontak baru ke server, lalu menempatkan kontak ini dalam grup baru, adaptor sinkronisasi harus menambahkan grup baru ke tabel ContactsContract.Groups. Grup tempat kontak mentah berada disimpan dalam tabel ContactsContract.Data, menggunakan jenis MIME ContactsContract.CommonDataKinds.GroupMembership.

Jika Anda mendesain adaptor sinkronisasi yang akan menambahkan data kontak mentah dari server ke Penyedia Kontak, dan Anda tidak menggunakan grup, Anda harus memberi tahu Penyedia untuk membuat data Anda terlihat. Dalam kode yang dijalankan saat pengguna menambahkan akun ke perangkat, perbarui baris ContactsContract.Settings yang ditambahkan Penyedia Kontak untuk akun tersebut. Di baris ini, tetapkan nilai kolom Settings.UNGROUPED_VISIBLE ke 1. Jika Anda melakukannya, Penyedia Kontak akan selalu membuat data kontak Anda terlihat, meskipun Anda tidak menggunakan grup.

Foto kontak

Tabel ContactsContract.Data menyimpan foto sebagai baris dengan jenis MIME Photo.CONTENT_ITEM_TYPE. Kolom CONTACT_ID baris ditautkan ke kolom _ID kontak mentah yang memilikinya. Class ContactsContract.Contacts.Photo menentukan sub-tabel ContactsContract.Contacts yang berisi informasi foto untuk foto utama kontak, yang merupakan foto utama dari kontak mentah utama kontak. Demikian pula, class ContactsContract.RawContacts.DisplayPhoto menentukan sub-tabel ContactsContract.RawContacts yang berisi informasi foto untuk foto utama kontak mentah.

Dokumentasi referensi untuk ContactsContract.Contacts.Photo dan ContactsContract.RawContacts.DisplayPhoto berisi contoh pengambilan informasi foto. Tidak ada class praktis untuk mengambil thumbnail utama untuk kontak mentah, tetapi Anda dapat mengirim kueri ke tabel ContactsContract.Data, memilih _ID kontak mentah, Photo.CONTENT_ITEM_TYPE, dan kolom IS_PRIMARY untuk menemukan baris foto utama kontak mentah.

Data feed sosial untuk seseorang juga dapat menyertakan foto. Foto ini disimpan dalam tabel android.provider.ContactsContract.StreamItemPhotos, yang dijelaskan secara lebih mendetail di bagian Foto feed sosial.