Провайдери контенту
Наш додаток може зберігати різноманітну інформацію про користувача, якісь пов'язані дані у файлах чи налаштуваннях. Однак ОС Android уже зберігає низку важливої інформації, пов'язаної з користувачем, до якої маємо доступ і яку ми можемо використовувати. Це і списки контактів, і файли збережених зображень та відеоматеріалів, і якісь позначки про дзвінки тощо, тобто певний контент. А для доступу до цього контенту в OC Android визначені провайдери контенту (content provider)
В Android є такі вбудовані провайдери, визначені в пакеті android.content:
AlarmClock: управління будильникомBrowser: історія браузера та закладкиCalendarContract: каледар та інформація про подіїCallLog: інформація про дзвінкиContactsContract: контактиMediaStore: медіа-файлиSearchRecentSuggestions: підказки щодо пошукуSettings: системні налаштуванняUserDictionary: словник слів, які використовуються для швидкого наборуVoicemailContract: записи голосової пошти
Робота з контактами
Контакти в Android мають вбудований API, який дозволяє отримувати і змінювати список контактів. Усі контакти зберігаються в базі даних SQLite, однак вони не становлять єдиної таблиці. Для контактів відведено три таблиці, пов'язаних відношенням один-до-багатьої: таблиця для зберігання інформації про людей, таблиця їхніх телефонів і таблиця адрес їхніх електронних пошт. Але завдяки Android API ми можемо абстрагуватися від зв'язків між таблицями.
Загальна форма отримання контактів має такий вигляд:
ArrayList<String> contacts = new ArrayList<String>();
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if(cursor!=null) {
while (cursor.moveToNext()) {
// отримуємо кожен контакт
String contact = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
// додаємо контакт до списку
contacts.add(contact);
}
cursor.close();
}
Усі контакти та супутній функціонал зберігаються в спеціальних базах даних SQLite. Але нам не треба безпосередньо працювати з ними. Ми можемо скористатися об'єктом класу Cursor. Щоб його отримати, спочатку викликається метод getContentResolver(), який повертає об'єкт ContentResolver. Потім по ланцюжку викликається метод query(). У цей метод передається низка параметрів, перший з яких представляє URI - ресурс, який ми хочемо отримати. Для звернення до бази даних контактів використовується константа ContactsContract.Contacts.CONTENT_URI
Метод contactsCursor.moveToNext() дає змогу послідовно переміщатися по записах контактів, зчитуючи по одному контакту через виклик contactsCursor.getString().
Таким чином, отримувати контакти не складно. Головна складність у роботі з контактами, та й з будь-якими іншими провайдерами контенту, полягає у встановленні дозволів. До Android API 23 достатньо було встановити відповідний дозвіл у файлі маніфесту додатка. Починаючи ж з API 23 (Android Marshmallow) Google змінив схему роботи з дозволами. І тепер користувач сам повинен вирішити, чи буде він давати дозволи додатку. У зв'язку з чим розробники повинні додавати додатковий код.
Отже, для доступу до контактів нам треба встановити дозвіл android.permission.READ_CONTACTS у файлі маніфесту додатка:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contactsapp">
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ContactsApp">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Для виведення списку контактів у файлі activity_main.xml визначимо таку розмітку інтерфейсу:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Контакты"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@id/contactList"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ListView
android:id="@+id/contactList"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header" />
</androidx.constraintlayout.widget.ConstraintLayout>
Для виведення списку контактів скористаємося елементом ListView. І в класі MainActivity отримаємо контакти:
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE_READ_CONTACTS=1;
private static boolean READ_CONTACTS_GRANTED =false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// отримуємо дозволи
int hasReadContactPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS);
// якщо пристрій до API 23, встановлюємо роздільну здатність
if(hasReadContactPermission == PackageManager.PERMISSION_GRANTED){
READ_CONTACTS_GRANTED = true;
}
else{
// викликаємо діалогове вікно для встановлення дозволів
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE_READ_CONTACTS);
}
// якщо дозвіл встановлено, завантажуємо контакти
if (READ_CONTACTS_GRANTED){
loadContacts();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_READ_CONTACTS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
READ_CONTACTS_GRANTED = true;
}
}
if(READ_CONTACTS_GRANTED){
loadContacts();
}
else{
Toast.makeText(this, "Потрібно встановити дозволи", Toast.LENGTH_LONG).show();
}
}
private void loadContacts(){
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
ArrayList<String> contacts = new ArrayList<String>();
if(cursor!=null){
while (cursor.moveToNext()) {
// отримуємо кожен контакт
String contact = cursor.getString(
cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
// додаємо контакт до списку
contacts.add(contact);
}
cursor.close();
}
// створюємо адаптер
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, contacts);
ListView contactList = findViewById(R.id.contactList);
// встановлюємо для списку адаптер
contactList.setAdapter(adapter);
}
}
Крім власне завантаження контактів і передавання їх через адаптер ArrayAdapter у список ListView тут додано багато коду з управління дозволами. Спочатку визначено змінну READ_CONTACTS_GRANTED, яка вказує, чи було видано дозвіл. І тут є два варіанти дій.
Перший варіант передбачає, що пристрій має версію Android, нижчу за Marshmallow (нижче API 23). Для цього ми просто дізнаємося, чи є дозвіл для READ_CONTACTS і якщо він є, то встановлюємо для змінної READ_CONTACTS_GRANTED значення true:
int hasReadContactPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS);
if(hasReadContactPermission == PackageManager.PERMISSION_GRANTED){
READ_CONTACTS_GRANTED = true;
}
Інакше нам треба відобразити користувачеві діалогове вікно, де він має вирішити, чи треба дати додатку дозвіл:
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},
REQUEST_CODE_READ_CONTACTS);
У цей метод передаються три параметри. Перший - поточний контекст, тобто поточний об'єкт Activity.
Другий параметр представляє набір дозволів, які треба отримати, у вигляді масиву рядків. Нам треба отримати в даному випадку тільки один дозвіл - Manifest.permission.READ_CONTACTS.
Третій параметр представляє код запиту, через який ми зможемо отримати відповідь користувача.
Якщо ми хочемо отримати вибір користувача під час встановлення дозволів, то нам треба перевизначити в класі Activity метод onRequestPermissionsResult:
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_READ_CONTACTS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
READ_CONTACTS_GRANTED = true;
}
}
if(READ_CONTACTS_GRANTED){
loadContacts();
}
else{
Toast.makeText(this, "Потрібно встановити дозволи", Toast.LENGTH_LONG).show();
}
}
Перший параметр методу requestCode - це той код запиту, який передавався як третій параметр в ActivityCompat.requestPermissions(). Другий параметр - масив рядків, для яких встановлювалися дозволи. Тобто одномоментно ми можемо встановлювати відразу кілька дозволів.
Третій параметр власне зберігає числові коди дозволів. Оскільки ми запитуємо тільки один дозвіл, то перший елемент масиву буде зберігати його код. Через умовний вираз ми можемо перевірити цей код: grantResults[0] == PackageManager.PERMISSION_GRANTED. І залежно від результату перевірки змінити змінну READ_CONTACTS_GRANTED.
І під час запуску програми нам спочатку відобразиться вікно для видачі дозволу, а після видачі підтверджень - список контактів:

Після видачі дозволу під час повторних запусків застосунку повторювати дозвіл не потрібно, тому метод onRequestPermissionsResult() у такому разі спрацьовуватиме тільки один раз. А змінна READ_CONTACTS_GRANTED у цьому випадку вже матиме значення true.
Інша ситуація - якщо ми відхилимо дозвіл. У цьому разі під час повторного запуску застосунку повторно відображатиметься це вікно.