Введение

SDK android дает богатые возможности для работы с телефоном. Есть возможность получать фотографии, отслеживать перемещения телефона, записывать звук с микрофона, перехватывать sms и т.д. Перечисленные возможности попадают под категорию конфиденциальные. К сожалению, до недавнего времени, единственный способ узнать какие разрешения будет использовать приложение, можно было увидеть только в диалоговом окне при установке приложения. На практике, данный подход оказался неэффективен, т.к. пользователь обычно не читает и автоматически соглашается с установкой. В результате в google play появилась куча приложений типа “Фонарик”, которые получали доступ к разрешениям которые совершенно не нужных для выполнения своих задач.

С выходом android 6 (API level 23) ситуация изменилась. Появились разрешения, которые необходимо принимать при работе приложения. Работает это точно так же как и в iOS. При запуске приложения появляется системный диалог, который и предлагает пользователю разрешить или запретить доступ приложения к разрешению.

Виды Permissions

Теперь разрешения разделены на два типа:

  • обычные (normal);
  • опасные (dangerous

Обычные разрешения - это группа разрешений, которые не связаны с приватными данными. Они описываться в manifest.xml и принимаются пользователем автоматически при установки приложения. Опасные разрешения - дают доступ к приватным данным пользователя.

Список опасных разрешений:

READ_CALENDAR

WRITE_CALENDAR

CAMERA

READ_CONTACTS

WRITE_CONTACTS

GET_ACCOUNTS

ACCESSFINELOCATION

ACCESSCOARSELOCATION

RECORD_AUDIO

READPHONESTATE

CALL_PHONE

READCALLLOG

WRITECALLLOG

ADD_VOICEMAIL

USE_SIP

PROCESSOUTGOINGCALLS

BODY_SENSORS

SEND_SMS

RECEIVE_SMS

READ_SMS

RECEIVEWAPPUSH

RECEIVE_MMS

READEXTERNALSTORAGE

WRITEEXTERNALSTORAGE

С версии android API level 23, необходимо обязательно запрашивать разрешение у пользователя, иначе приложение упадет с исключением:

Также у пользователя появилась возможность в любой момент можно дать или запретить разрешение. Для этого в настройках приложения появился новый пункт меню “Разрешения”.

Когда следует запрашивать разрешения

Базовые разрешения, без которых приложение не сможет запуститься следует запрашивать при старте приложения. Остальные разрешения следует запрашивать только перед непосредственным использованием.

Постановка задачи

Создадим простое приложение, которое по нажатию на кнопку будет выводить список имён из адресной книги телефона.

Создание проекта

Создадим новый проект с Empty Activity. В activity_main.xml создадим одну кнопку и ListView.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_permission_read_contacts"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text_read_contacts"/>

    <ListView
        android:id="@+id/list_contacts"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

Добавим строковый ресурсы в strings.xml:

<string name="text_read_contacts">Read contacts</string>

А также добавим разрешение в manifest.xml:

<uses-permission android:name="android.permission.READ_CONTACTS" />

Инициализируем view компоненты и повесим обработчик нажатия на кнопку.

private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 1234;
private ListView mListContacts;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button buttonReadContacts = (Button) findViewById(R.id.btn_permission_read_contacts);
        mListContacts = (ListView) findViewById(R.id.list_contacts);

        buttonReadContacts.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getContacts();
            }
        });
    }

public void getContacts(){
        //Проверяем есть ли у нас разрешение на чтение контактов
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.READ_CONTACTS)) {
                //Здесь выводим сообщение в котором поясняем почему необходимы разрешение
                Toast.makeText(this,"Need for show contacts. Please turn on permission at [Setting]>[Permission]", Toast.LENGTH_SHORT).show();
            } else {
                //Запрашиваем разрешение
                ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);
            }
            return;
        }

        Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
        if(phones == null){
            return;
        }

        List<String> nameList = new ArrayList<>();
        while (phones.moveToNext()) {
            String user = "";
            user = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
            nameList.add(user);
        }
        phones.close();

        ArrayAdapter<String> adapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1
, android.R.id.text1, nameList);
        mListContacts.setAdapter(adapter);

    }

Обработать результат запроса разрешения следует в onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults), где requestCode есть id который был передан в ActivityCompat.requestPermissions().

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //Здесь мы получаем разрешение и снова вызываем getContacts()
                    getContacts();
                } else {
                    Toast.makeText(this, "Read contacts permission is denied.", Toast.LENGTH_SHORT).show();
                }
                return;
            }
        }
    }

Заключение

Android Runtime Permissions в первую очередь направленно на улучшение безопасности персональных данных и привлечения внимания пользователя к разрешениям, которые использует приложение.

Ссылка на GitHub репозиторий.