Реализуем выбор файла (File Picker). Система разрешений Android. Получение списка файлов.

Урок #16

Урок 16 — Реализуем выбор файла (File Picker). Система разрешений Android. Получение списка файлов.

Итак, мы продолжаем создание приложения, которое позволяет просматривать список установленных приложений, а так же удалять их и устанавливать новые. Мы остановились на том, что начали реализовывать Activity, которая позволит нам выбрать файл для установки.

Прежде чем предоставить пользователю список файлов для выбора, нужно получить этот список.

Система разрешений Android

Приложение для Android может получить доступ к множеству вещей в системе, одна из этих вещей — файлы.

Для того, чтобы пользователь знал, что именно может делать приложение, а так же мог контролировать доступ приложения к функциям системы существует система разрешений.

Приложение должно запросить разрешение (Permission) пользователя, прежде чем оно получит доступ к определенным функциям системы. Разрешения могут быть явными или неявными, метод получения зависит от типа разрешения. Существует два типа разрешений:

  • Обычные — разрешения, требующиеся для получения данных, которые не опасны для "чувствительных" данных пользователя. Например, разрешение для доступа в интернет. Такие разрешения просто декларируются в AndroidManifest.xml, пользователь видит их при установке приложения.
  • Опасные — разрешения, требующиеся для получения данных, затрагивающих приватные данные пользователя (как запись, так и чтение). Пример таких данных — контакты, файлы на SD-карте (реальной или виртуальной). Когда приложение запрашивает такое разрешение, пользователю показывается диалог, в котором он может предоставить доступ или отказать в нём.

Все опасные разрешения разделены на соответствующие группы разрешений. Например, приложение запрашивает доступ к изменению контактов, и при этом пользователь уже одобрил чтение контактов — в таком случае, система разрешит доступ к изменению контактов без запроса через диалог.

В случае, если пользователь отказывает в разрешении, приложение может запросить его повторно, и диалог покажется снова. При этом в диалоге будет присутствовать галочка, позволяющая больше не спрашивать пользователя об этом разрешении.

Кроме того, пользователь всегда может подтвердить или же отозвать разрешение в настройках.

Давайте запросим разрешение на получение списка файлов.

В первую очередь, добавим соответствующее разрешение в AndroidManifest.xml.

Разрешения добавляются с использованием тэга uses-permission с атрибутом android:name, в котором указывается название разрешения, которое мы хотим получить. Добавляется этот элемент перед тэгом application.

Нам нужно разрешение android.permission.READ_EXTERNAL_STORAGE, поэтому наш манифест будет выглядеть так:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.packages">

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

    <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/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".FilePickerActivity" />

    </application>

</manifest>

Теперь в FilePickerActivity создайте метод initFileManager():

private void initFileManager() {

}

В нём мы в первую очередь проверим, есть ли у нас разрешение на чтение файлов.

Делается это вызовом метода ContextCompat.checkSelfPermission(), который возвращает PackageManager.PERMISSION_GRANTED, если пользователь предоставил разрешение, или PackageManager.PERMISSION_DENIED, если пользователь отказал в нём.

Если разрешения нет, то нужно использовать метод ActivityCompat.requestPermissions(), чтобы запросить разрешение. С помощью этого метода можно запросить несколько разрешений подряд.

Итак, добавьте в FilePickerActivity следующий код:

private void requestPermissions() {
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            PERMISSION_REQUEST_CODE
    );
}

В этом методе мы запрашиваем разрешение.

Теперь добавьте такой код в метод initFileManager():

if (ContextCompat.checkSelfPermission(this,
        Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
    // Разрешение предоставлено
    fileManager = new FileManager(this);
} else {
    requestPermissions();
}

Кроме того, в классе FilePickerActivity нужно создать константу PERMISSION_REQUEST_CODE — когда пользователь предоставит разрешение, нам придёт уведомление, и по этому коду мы сможем понять, какое именно разрешение было предоставлено (если мы запрашивали несколько):

private static final int PERMISSION_REQUEST_CODE = 1;

А теперь обработаем событие предоставления разрешения. Для этого добавьте следующий метод:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

В этом методе мы должны проверить, предоставил ли пользователь разрешение:

if (requestCode == PERMISSION_REQUEST_CODE) {
    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        Log.i(TAG, "Permission granted!");
        initFileManager();
    } else {
        Log.i(TAG, "Permission denied");
        requestPermissions(); // Запрашиваем ещё раз
    }
}

Ну и теперь вызываем initFileManager() в onCreate():

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_file_picker);
    initFileManager();
}

Теперь запустите приложение и откройте экран выбора файла:

Запрос разрешения
Запрос разрешения

Если вы нажмёте на кнопку Deny, диалог покажется ещё раз, и будет показываться до тех пор, пока пользователь не нажмёт Allow или не запретит запрашивать разрешение.

На самом деле, бесконечно запрашивать разрешение — это не очень хорошая практика. В случае, если пользователь не подтвердил доступ с первого раза, нужно показать ему информацию о том, зачем нам нужно это разрешение, и что приложение не будет без него корректно работать, после чего предложить нажать на кнопку, чтобы показать диалог ещё раз.

Но для наших нужд в данный момент это не нужно, поэтому оставим пока что такой вариант.

FileManager

Теперь создадим класс, который будет ответственен за получение списка файлов. В нём должна храниться текущая директория, а так же должна быть возможность получения списка файлов в текущей директории с возможностью сортировки.

Создайте новый класс FileManager:

public class FileManager {

}

Объявите в нём поле currentDirectory:

private File currentDirectory;

Так же пригодится поле rootDirectory, в котором мы укажем директорию, выше которой подниматься будет нельзя (это будет та же директория, что и currentDirectory при создании):

private final File rootDirectory;

Теперь создайте конструктор. В конструкторе мы должны проинициализировать currentDirectory.

По умолчанию это будет корень SD-карты. Однако, если SD-карта недоступна, то будем использовать директорию приложения. Для доступа к директории приложения нам понадобится контекст.

Конструктор в итоге будет выглядеть вот так:

public FileManager(Context context) {
    File directory;
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
        directory = Environment.getExternalStorageDirectory();
    } else {
        directory = ContextCompat.getDataDir(context);
    }
    rootDirectory = directory;
    navigateTo(directory);
}

Сперва мы проверяем, доступна ли SD-карта. Если доступна, то получаем её, используя метод Environment.getExternalStorageDirectory(). В ином случае получаем путь к директории данных нашего приложения (в нашем случае это будет /data/data/com.test.packages/).

Обратите внимание: несмотря на то, что в большинстве случаев SD-карта доступна по пути /sdcard/, ни в коем случае нельзя использовать эту строковую константу. Вместо этого нужно всегда использовать Environment.getExternalStorageDirectory(), иначе может получиться так, что доступа к карте вы не получите! То же самое справедливо и для директории данных, которую мы используем, если карта недоступна.

В последней строчке мы переходим в нужную нам директорию, используя метод navigateTo(). Этот метод мы ещё не создали — самое время сделать это:

public boolean navigateTo(File directory) {
    // Проверим, является ли файл директорией
    if (!directory.isDirectory()) {
        Log.e(TAG, directory.getAbsolutePath() + " is not a directory!");
        return false;
    }

    // Проверим, не поднялись ли мы выше rootDirectory
    if (!directory.equals(rootDirectory) &&
            rootDirectory.getAbsolutePath().contains(directory.getAbsolutePath())) {
        Log.w(TAG, "Trying to navigate upper than root directory to " + directory.getAbsolutePath());
        return false;
    }

    currentDirectory = directory;

    return true;
}

Отлично! Теперь нужно создать метод для навигации в директорию, которая находится выше. Пользователь будет попадать в неё, нажимая кнопку назад:

public boolean navigateUp() {
    return navigateTo(currentDirectory.getParentFile());
}

Ну и напоследок — метод, с помощью которого мы будем получать список файлов в текущей директории:

public List<File> getFiles() {
    List<File> files = new ArrayList<>();
    files.addAll(Arrays.asList(currentDirectory.listFiles()));

    return files;
}

На данный момент может показаться, что этот метод избыточен, но позже вы поймёте, что мы создали его не просто так :)

Отображаем список файлов

Пришло время отобразить список файлов. Процесс почти ничем не отличается от того, что мы делали ранее с установленными приложениями.



Продолжение доступно на платных тарифах

А вместе с ним — проверка домашних заданий нашими менторами.

Это совсем недорого — всего от 440 ₽ в месяц!



ВЫБРАТЬ ТАРИФ



Вход

Войдите, чтобы пользоваться всеми преимуществами.
Это займёт всего пару секунд!

или