Руководство по использованию myDSS SDK

Термины и определения

Тип Описание
DSSUser Объект, содержащий всю необходимую информацию для выполнения действий от имени пользователя. Содержит, в том числе, информацию для доступа к экземпляру DSS (url, корневой сертификат и пр.), "вектор аутентификации" для подтверждения действий и пр. С точки зрения DSS данный объект является устройством, подключенным к учетной записи пользователя DSS
DSSDevice Объект, содержащий информацию об устройствах, подключенных к той же учетной записи, что и объект DSSUser. Данный объект не содержит "вектор аутентификации" и не может использоваться для подтверждения операций или выполнения других действий
DSSOperation Операция DSS, для которой требуется подтверждение/отклонение. Операция может содержать несколько документов. При подтверждении/отклонении операции все содержащиеся в ней документы будут подписаны/отклонены. Операция создается на сервере DSS
DSSOperation.DSSDocument Документ, входящий в операцию, либо обрабатываемый самостоятельно
Document Description "Сниппет" документа, сформированный сервером DSS на основе шаблона для сниппета и содержания документа
Document Preview Визуализированный в человеко-читаемую форму документ, сформированный сервером DSS на основе шаблона для визуализации и содержания документа
Document RawPDF "Сырое" содержание документа (например, текстового файла), преобразованное в формат PDF
DSSCertificate Сертификат, привязанный к ключу подписи в учетной записи на DSS

Использование библиотеки myDSS на Android

Включение библиотеки в приложение

Минимальная версия Android SDK, необходимая для работы библиотеки - API Level 16 (Android 4.1., Jelly Bean)

Добавление библиотеки в качестве зависимости

Библиотека MyDSS может быть добавлена в проект в качестве зависимости из maven-репозитория.

Пример корневого файла build.gradle проекта:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()

        maven {
            url "https://repo.paycontrol.org/mydss/android/maven"
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Добавление зависимости:

    implementation 'ru.cryptopro.sdk:mydss:<Последняя версия>'

Номер последней версии можно найти в репозитории.

Настройка манифеста приложения

Для работы библиотеки необходимо добавить соответствующие разрешения (для работы с камерой и сетевыми соединениями) в манифест приложения:

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

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>

Настройка правил минимизации и обфускации

Если в проекте используются инструменты минификации и обфускации proguard или D8, то для корректной работы библиотеки в файл правил proguard (например, proguard-rules.pro) необходимо добавить следующие ограничения:

-keep public class ru.cryptopro.mydss.** { *; }
-dontwarn ru.cryptopro.mydss.**
-keep public class ru.CryptoPro.** { *; }
-dontwarn ru.CryptoPro.**
-keep public class ru.cprocsp.** { *; }
-dontwarn ru.cprocsp.**
-keep public class org.ini4j.spi.** { *; }
-dontwarn org.ini4j.spi.**
-keep public class java.awt.event.** { *; }
-dontwarn java.awt.event.**
-keep public class javax.swing.** { *; }
-dontwarn javax.swing.**
-keep public class com.objsys.asn1j.runtime.** { *; }
-dontwarn com.objsys.asn1j.runtime.**
-keep class com.shockwave.**

При отсутствии файла proguard-rules.pro в проекте его необходимо создать и добавить в конфигурацию конечной сборки в файле build.gradle:

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        proguardFiles fileTree('proguard').asList().toArray()
    }
    debug {
        minifyEnabled false
        debuggable true
    }
}

Инициализация библиотеки при запуске

В начале работы приложения необходимо инициализировать библиотеку при помощи статического метода MyDSS.init.

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import ru.cryptopro.mydss.sdk.v2.MyDss;

public class MainActivity extends AppCompatActivity {

    static MyDss myDSS = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MyDss.init( getApplicationContext(), 
                    MyDss.RootCertificateType.Development, // Тип корневого сертификата
                    MyDss.DSS_LOG_DEBUG,    // устанавливается уровень логирования библиотеки в LogCat: DSS_LOG_DEBUG или DSS_NO_LOGGING
                    null, // Список "доверенных" приложений
                    new DSSInitCallback() 
        {
            @Override
            public void error(DSSError dssError) {
                // Инициализация прошла неудачно. Библиотекой пользоваться нельзя
                Log.e("ERROR", dssError.getMessage());
            }

            @Override
            public void success(MyDss myDssInstance) {
                // Библиотека инициализирована успешно. Необходимо сохранить созданный экземпляр myDssInstance для дальнейшего использования
                myDSS = myDssInstance;
            }
        });
    }
}

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

Приложение может использовать два корневых сертификата: для разработки и для конечного приложения.

Сертификат для разработки должен быть доступен в приложении как ресурс с идентификатором R.raw.development_root_cert (для этого, например, сохраните сертификат под именем res/raw/development_root_cert.crt).

Сертификат для конечного приложения должен быть быть доступен в приложении как ресурс с идентификатором R.raw.production_root_cert (для этого, например, сохраните сертификат под именем res/raw/production_root_cert.crt).

Сертификаты должны быть сохранены в формате PEM.

Кроме того, в одном файле ресурса можно разместить несколько сертификатов. Тогда файл ресурса будет выглядеть следующим образом:

-----BEGIN CERTIFICATE-----
Base64 encoded certificate 1
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
Base64 encoded certificate 2
-----END CERTIFICATE-----

...

-----BEGIN CERTIFICATE-----
Base64 encoded certificate N
-----END CERTIFICATE-----

ЗАМЕЧАНИЕ
В примере инициализации выше в качестве списка "доверенных" приложений передано пустое значение (null). Во время инициализации SDK выполняет поиск установленных приложений с привилегиями android.permission.PACKAGE_USAGE_STATS и android.permission.SYSTEM_ALERT_WINDOW. Такие приложения являются потенциально опасными, так как могут рисовать окна "поверх" окон других процессов, в результате чего пользователь может видеть на экране искажённую информацию.
Прежде чем процесс инициализации SDK будет завершён, пользователю будет показано диалоговое окно со списком потенциально опасных программ. При этом в SDK есть предопределённый белый список приложений с вышеуказанными привилегиями, которые не являются опасными (этот список включает системные приложения и некоторые другие). В силу непрерывного развития мобильной индустрии данный список не является исчерпывающим, поэтому вы можете пополнять его другими приложениями, которые на ваш взгляд SDK ошибочно определяет как вредоносные.
Подробнее о формировании списка "доверенных" приложений смотрите в разделе Инициализация SDK со списком доверенных приложений.

Структура

Основные классы для работы:

  • DSSUsersManager: Создаёт и управляет учётными записями пользователя на устройстве
  • DSSOperationsManager: Получает информацию и подписывает операции и документы
  • DSSCertificatesManager: Управляет сертификатами пользователей
  • DSSDevicesManager: Управляет устройствами, привязанными к учётной записи
  • DSSPolicyManager: Получает информацию о сервере DSS

Начало работы

Перед тем, как начать подписывать документы и операции, нужно создать пользователя. Есть 3 способа это сделать:

  1. Создать онлайн с подтверждением
  2. Создать с использованием QR-кода
  3. Создать онлайн с привязкой к другому устройству

Создание пользователя онлайн с подтверждением

DSSUsersManager.createDSSUser(
    String externalId,  // Идентификатор сущности вызывающего приложения,
                        // к которому будет "привязан" объект 
    String alias,       // Человекочитаемый идентификатор устройства
    String name,        // Имя ключа
    String serviceUrl,  // URL для взаимодействия с myDSS
    String deviceName,  // Читаемое название устройства
    PushNotificationData pushData,  // Данные для получения PUSH-уведомлений
    final boolean requirePassword,  // Требуется ли установка пароля                          
    final DSSUserCallback callback  // Callback для обработки результатов  
)

В качестве параметра callback можно передать анонимный класс:

new DSSUserCallback() {
    @Override
    public void error(DSSError error) {
        // Возникла ошибка
    }

    @Override
    public void success(DSSUser user) {
        // Пользователь создан успешно, данные сохранены в объекте user
    }

    @Override
    public void error(DSSNetworkError error) {
        // Возникла ошибка при отправке сетевых запросов
    }
}

В качестве данных для получения PUSH-уведомлений необходимо передать экземпляр класса PushNotificationData, например, создать его через конструктор, передав в него token, полученный от сервиса FireBase:

PushNotificationData pushData = new PushNotificationData("my firebase device token");

Пользователь создан и сохранён в хранилище, сейчас его статус — DSSDevice.DSSDeviceStatus.Installed. Теперь нужно дождаться, когда его статус на сервере DSS сменится на DSSDevice.DSSDeviceStatus.NotVerified. Чтобы проверить текущий статус:

DSSUsersManager.updateStatus(user, new DSSUserCallback() {
    @Override
    public void error(DSSError error) {

    }

    @Override
    public void success(DSSUser user) {
        // Статус успешно обновился, теперь можно проверить текущий статус
        if (user.getStatus() == DSSDevice.DSSDeviceStatus.NotVerified) {
            // Статус сменился
        }
    }

    @Override
    public void error(DSSNetworkError error) {

    }
});

Как только статус пользователя станет DSSDevice.DSSDeviceStatus.NotVerified, мы сможем его верифицировать:

DSSUsersManager.acceptAccountChanges(user, new DSSUserCallback() {
    @Override
    public void error(DSSError error) {

    }

    @Override
    public void success(DSSUser user) {
        // Подтверждение успешно. Статус пользователя - DSSDevice.DSSDeviceStatus.Active
    }

    @Override
    public void error(DSSNetworkError error) {

    }
});

При верификации может потребоваться QR-код. Это зависит от настроек политики сервера.

Теперь статус пользователя DSSDevice.DSSDeviceStatus.Active, с ним можно работать дальше.

Создание с использованием QR-кода

DSSUsersManager.createDSSUserWithInitQR(
    String externalId,  // Идентификатор сущности вызывающего приложения,
                        // к которому будет "привязан" объект 
    String alias,       // Человекочитаемый идентификатор устройства
    String name,        // Имя ключа
    String deviceName,  // Читаемое название устройства
    PushNotificationData pushData,  // Данные для получения PUSH-уведомлений
    final boolean requirePassword,  // Требуется ли установка пароля                          
    final DSSUserCallback callback  // Callback для обработки результатов  
)

При создании пользователя данным способом потребуется отсканировать QR-код, сгенерированый сервером. После создания статус пользователя — DSSDevice.DSSDeviceStatus.NotVerified, с ним можно работать дальше.

Создание пользователя онлайн с привязкой к другому устройству

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

Получить идентификатор можно у экземпляра пользователя:

// Получить дентификатор подтверждённого пользователя, к которому привязываем новое устройство
String uid = user.getDSSUserId();

Процесс добавления нового устройства представляет собой следующий процесс:

  1. На добавляемом устройстве вызывается метод createDSSUserWithApproval класса DSSUsersManager, который создаст нового пользователя со статусом DSSDevice.DSSDeviceStatus.ApproveRequired.
  2. На добавляемом устройстве отображается QR-код, содержащий необходимую информацию о данном устройстве.
  3. На основном устройстве вызывается метод processAwaitingDevice класса DSSDevicesManager для выполнения процедуры подтвеждения или отклонения добавляемого устройства. При вызове этого метода пользователю будет предложено отсканировать QR-код с экарана добавляемого устройства.
  4. После сканирования QR-кода и просмотра информации о добавляемом устройстве пользователь выполняет подтверждение или отклонение добавляемого устройства.
  5. На добавляемом устройстве на экране c QR-кодом отображается кнопка проверки статуса добавляемого устройства. Пользователь может покинуть экран, дождавшись подтверждения с основного устройства, или сделать это сразу. В зависимости от этого, в приложение будет возвращён пользователь со статусом DSSDevice.DSSDeviceStatus.ApproveRequired или DSSDevice.DSSDeviceStatus.Active.

Итак, сначала на новом устройстве вызываем метод:

DSSUsersManager.createDSSUserWithApproval(
    String externalId,              // Идентификатор сущности вызывающего приложения, к которому будет "привязан" объект
    String alias,                   // Человекочитаемый идентификатор устройства
    String name,                    // Имя для сохранения учетной записи
    String serviceUrl,              // URL для взаимодействия с myDSS
    String deviceName,              // Читаемое название устройства
    PushNotificationData pushData,  // Данные для получения PUSH-уведомлений
    String uid,                     // Идентификатор пользователя, к которому привязываем устройство
    final boolean requirePassword,  // Требуется ли установка пароля
    final DSSUserCallback callback  // Callback для обработки результатов
);

При вызове метода создаётся новый пользователь со статусом DSSDevice.DSSDeviceStatus.ApproveRequired. Теперь требуется получить подтверждение с основного устройства.

DSSUsersManager.createDSSUserWithApproval(externalId, alias, name, serviceUrl, "my phone", new PushNotificationData("My push id"), uid, requirePassword, new DSSUserCallback() {
    @Override
    public void error(DSSError error) {

    }

    @Override
    public void success(DSSUser user) {
        // Созданный пользователь имеет статус DSSDevice.DSSDeviceStatus.ApproveRequired,
        // если добавление данного устройства ещё не было подтверждено на основном устройстве.
        // Если добавляемое устройство уже подтверждено, то пользователь имеет статус DSSDevice.DSSDeviceStatus.Active. 
    }

    @Override
    public void error(DSSNetworkError error) {

    }
});

Для подтверждения на основном устройстве необходимо вызвать метод processAwaitingDevice из класса DSSDevicesManager:

DSSDevicesManager.processAwaitingDevice(currentUser, new DSSSDKCallback() {
    @Override
    public void error(DSSError dssError) {

    }

    @Override
    public void success(DSSDevicesManager.Action action) {
        // Устройство успешно подтверждено или отклонено
    }

    @Override
    public void error(DSSNetworkError dssNetworkError) {

    }
});

На добавляемом устройстве проверить статус можно вручную. Это может потребоваться, например, если пользователь закрыл экран добавления устройства преждевременно и был возвращён статус DSSDevice.DSSDeviceStatus.ApproveRequired.

DSSUsersManager.checkApprovalStatus(user, new DSSUserCallback() {
    @Override
    public void error(DSSError error) {

    }

    @Override
    public void success(DSSUser user) {
        // Новое устройство подтверждено
    }

    @Override
    public void error(DSSNetworkError error) {

    }
});

Когда новое устройство подтверждено и статус пользователя — DSSDevice.DSSDeviceStatus.Active, то с ним можно работать на новом устройстве.

Получение списка пользователей и устройств

Пользователи, установленные на устройстве

Получить список всех пользователей, сохранённых на данном устройстве, можно следующим образом:

List<DSSUser> users = DSSUsersManager.listStorage();

Привязаные устройства

DSSDevicesManager.listDevices(user, new DSSDevicesNetworkCallback() {

    @Override
    public void success(DSSDevice[] devices) {
        // Список устройств получен
    }

    @Override
    public void error(DSSNetworkError error) {

    }
});

Работа с операциями

Для работы с операциями используются статические методы класса DSSOperationsManager.

Получение списка операций

DSSOperationsManager.getOperationsList(
    DSSUser user, // Пользователь, для которого проверяются операции
    DSSOperationsManager.OperationType operationType, // // Фильтр операций по типу
    String operationId, // Получение операции по ёё идентификатору
    DSSOperationsNetworkCallback callback // Callback для обработки результатов
)

Пример:

DSSOperationsManager.getOperationsList(
    user, 
    null,   // Не фильтруем по типу
    null,   // Не фильтруем по идентификатору
    new DSSOperationsNetworkCallback() {
        @Override
        public void success(DSSOperation[] operations) {
            // Список операций успешно получен
        }

        @Override
        public void error(DSSNetworkError error) {

        }
    });

Подтверждение операции

DSSOperationsManager.confirmOperation(
    DSSUser user,   // Пользователь, для которого подтверждаем операцию
    final DSSOperation operation, // Операция для подтверждения
    DSSOperationsManager.SignMode signMode, // Режим подтверждения - Offline или Online
    boolean isSelectionEnabled, // Установленный флаг позволяет выбирать 
                                // документы для подтверждения/отклонения
    boolean skipSnippet,    // Установленный флаг позволяет сразу отобразить 
                            // исходное представление документа в случае,
                            // если операция состоит только из одного документа.
    final DSSApproveRequestNetworkCallback callback // Callback для обработки результатов
)

Пример:

DSSOperationsManager.confirmOperation(user, operation, DSSOperationsManager.SignMode.Online, false, false, new DSSApproveRequestNetworkCallback() {
    @Override
    public void success(DSSOperationsManager.ApproveRequest approveRequest) {
        // Подтверждение операции прошло успешно
    }

    @Override
    public void error(DSSOperationsManager.ApproveRequest approveRequest) {
        // Во время подтверждения возникла ошибка
    }
});

Работа с документами

Загрузка документов

DSSOperationsManager.uploadDocument(DSSUser user,
    String title, // Заголовок документа
    String snippetTemplate, // HTML-сниппет документа
    String previewTemplate, // HTML-превью документа
    byte[] content, // Содержимое документа
    DSSDocumentIdNetworkCallback callback // Callback для обработки результатов
)

В качестве параметра callback можно передать анонимный класс:

new DSSDocumentIdNetworkCallback() {
    @Override
    public void success(String documentId) {
        // Документ загружен успешно, получили его идентификатор
    }

    @Override
    public void error(DSSNetworkError error) {
        // При загрузке возникла ошибка
    }
}

Подпись документов

DSSOperationsManager.signDocuments(
    DSSUser user, // Объект пользователя данного устройства
    List<String> documentIds, // Список идентификаторов документов в DSS для подписания
    DSSOperationsManager.SignParams params, // Параметры подписания
    final DSSSignResultNetworkCallback callback // Callback для обработки результатов
)

В качестве параметра callback можно передать анонимный класс:

new DSSSignResultNetworkCallback() {

    @Override
    public void error(DSSNetworkError error) {
        // Во время процедуры подписания возникла ошибка
    }

    @Override
    public void success(DSSOperationsManager.SignResults[] signResult) {
        // Возвращены результаты подписания для каждого документа
    }
}

Параметры подписания

Для параметров подписания нужно получить параметры сервера подписания и идентификатор сертификата:

// Получаем параметры сервера подписания 
DSSPolicyManager.getDSSSignParams(
    user,  // Пользователь, запрашивающий параметры сервера подписания
    new DSSPolicySignServerNetworkCallback() {
        @Override
        public void success(DSSSignServerParams signServerParams) {
            // Параметры получены, сохраняем для дальнейшего использования
            ...
        }

        @Override
        public void error(DSSNetworkError error) {

        }
    }
);
...

// Получаем сертификат
DSSCertificatesManager.listCertificates(user, new DSSCertificatesNetworkCallback() {
    @Override
    public void success(DSSCertificate[] certificates) {
        // Список сертификатов (и запросов на сертификаты) получен
        for (DSSCertificate cert: certificates) {
            // Достаём первый сертификат (проверяем, что это не запрос)
            if (cert.getType() == DSSCertificate.Type.Crt) {
                // Нашли сертификат, сохраняем
                ...
                break;
            }
        }
    }

    @Override
    public void error(DSSNetworkError error) {

    }
});

...

// Формируем параметры подписания
DSSOperationsManager.SignParams params = new DSSOperationsManager.SignParams(
    // DSSSignServerParams.ProcessingTemplateInfo.getId() возвращает int, приводим результат к String
    ""+signServerParams.getProcessingTemplates()[0].getId(),
    cert.getDssCertificateId()
);

Работа с сертификатами

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

Для начала, получаем параметры сервера подписания

DSSPolicyManager.getDSSSignParams(
    user,  // Пользователь, запрашивающий параметры сервера подписания
    new DSSPolicySignServerNetworkCallback() {
        @Override
        public void success(DSSSignServerParams signServerParams) {
            // Параметры получены, сохраняем для дальнейшего использования
            ...
        }

        @Override
        public void error(DSSNetworkError error) {

        }
    }
);

Далее подготавливаем параметры. Значения даны для примера

HashMap<String, String> dn = new HashMap<>();
dn.put("2.5.4.3", "TestCommonName");
int caId = 0;
String tid = "";

for (DSSSignServerParams.CaPolicies caPolicies : signServerParams.getCaPolicies()) {
    caId = caPolicies.getId();
    Set<String> keys = caPolicies.getEkuTemplates().keySet();
    for (String key : keys) {
        String[] list = caPolicies.getEkuTemplates().get(key);
        if (list != null && list.length > 0)
            tid = list[0];
        break;
    }
    break;
}

DSSCertificatesManager.createCertificate(user, caId, tid, dn, new DSSCertificateNetworkCallback() {
    @Override
    public void success(DSSCertificate certificate) {
        // Запрос на сертификат успешно создан
    }

    @Override
    public void error(DSSNetworkError error) {

    }
});

Проверка состояния сертификатов и запросов на сертификат

DSSCertificatesManager.listCertificates(user, new DSSCertificatesNetworkCallback() {
    @Override
    public void success(DSSCertificate[] certificates) {
        // Список сертификатов (и запросов на сертификаты) получен
        for (DSSCertificate cert: certificates) {
            // Выполняем нужные проверки
            if (cert.getType() == DSSCertificate.Type.Crt) {
                ... 
            }
        }
    }

    @Override
    public void error(DSSNetworkError error) {

    }
});

Персонализация пользовательского интерфейса

За внешний вид UI отвечает статический класс Appearance. С его помощью можно менять внешний вид кнопок, фоновых элементов, надписей, панелей инструментов (ToolBar), а также задавать перечень изображений для использования в интерфейсе.

public class Appearance {

    /// Картинки в интерфейсе
    private Images images;

    /// Внешний фоновых элементов
    private Views views;

    /// Внешний вид кнопок
    private Buttons buttons;

    /// Внешний вид надписей
    private Labels labels;

    /// Внешний вид тулбара
    private BaseAppearance baseAppearance;

    ...
}

Доступ к классу осуществляется при помощи метода getAppearance() класса MyDss:

public static Appearance getAppearance();

Пример изменения цвета основных кнопок:

// Изменение цвета основных кнопок
MyDss.getAppearance().getButtons().getPrimary().backgroundColor = R.color.dsssdk_color_red;

Персонализация изображений

Класс Appearance включает в себя несколько типов картинок для применения в разных местах интерфейса:

public static class Images {

    /// Появляется, когда код/пароль заполнен
    private ImageAppearance passedInputImage;

    /// Иконка удаления точки на клавиатуре
    private ImageAppearance keyboardRemove;

    /// Иконка отпечатка на клавиатуре
    private ImageAppearance keyboardFingerprint;

    /// Изображение устройства, появляется при предложении использовать FingerPrint
    private ImageAppearance fingerPrintSuggestionImage;

    /// Изображение кнопки информации об операции
    private ImageAppearance operationInfoIcon;

    /// Точки у предпросмотра документа
    private ImageAppearance dotsForOperations;

    /// Изображение индикатора для жестов
    private ImageAppearance scannerBackButton;

    ...
}

Класс ImageAppearance задаёт фоновый цвет и ресурс изображения для картинки:

static class ImageAppearance {

    /// Ресурс картинки
    public int icon;

    /// Цвет картинки
    public int iconTint;

    ...
}

Допустим, мы хотим задать ресурс изображения и фон индикатору для жестов:

MyDss.getAppearance().getImages().getScannerBackButton().icon = R.drawable.ic_back;
MyDss.getAppearance().getImages().getScannerBackButton().iconTint = R.color.dsssdk_color_white;

Персонализация надписей

Вложенный класс LabelAppearance задаёт различные параметры написания текста:

public static class LabelAppearance {

    enum FontWeight {
        Regular,
        Medium,
        Bold
    }

    /// Цвет надписи
    public int color;

    /// Размер надписи
    public float size;

    /// Толщина надписи
    public FontWeight fontWeight = FontWeight.Regular;

    /// Шрифт надписи
    public String font;

    ...
}

Следующие элементы интерфейса заданы классом LabelAppearance:


public static class Labels {

    /// Заголовок H1
    private LabelAppearance h1;

    /// Заголовок H2
    private LabelAppearance h2;

    /// Подзаголовок
    private LabelAppearance subtitle;

    /// Ключ в ячейке
    private LabelAppearance key;

    /// Значение в ячейке
    private LabelAppearance value;

    /// Заголовок для модальных окон
    private LabelAppearance modalTitle;

    /// Описание для модальных окон
    private LabelAppearance modalDescription;

    /// Ошибка
    private LabelAppearance error;

    /// Поле для ввода
    private LabelAppearance editText;

    /// Сообщение на экране сканирования QR-кода
    private LabelAppearance scannerError;

    /// Заголовок для выезжающего меню
    private LabelAppearance sliderTitle;

    ...
}

Кроме того, LabelAppearance позволяет настроить вид текста на кнопках и в заголовке панели инструментов.

Пример задания цвета и начертания шрифта для заголовка модальных окон:

MyDss.getAppearance().getLabels().getModalTitle().color = R.color.dsssdk_color_dark_black;
MyDss.getAppearance().getLabels().getModalTitle().fontWeight = Appearance.LabelAppearance.FontWeight.Medium;

Персонализация кнопок

Структура внешнего вида кнопки описывается классом ButtonAppearance:

static class ButtonAppearance {

    /// Шрифт заголовка
    private LabelAppearance appearance;

    public LabelAppearance getAppearance() {
        return appearance;
    }

    /// Цвет кнопки
    public int backgroundColor;

    /// Цвет кнопки при нажатом состоянии
    public int backgroundColorPressed;

    /// Радиус скругления углов
    public int cornerRadius;

    ...
}

Следующие типы кнопок используются в интерфейсе:

public static class Buttons {

    /// Основные кнопки
    private ButtonAppearance primary;

    /// Вторичные кнопки
    private ButtonAppearance secondary;

    /// Основная кнопка для модального окна
    private ButtonAppearance modalPrimary;

    /// Дополнительная кнопка для модального окна
    private ButtonAppearance modalSecond;

    /// Кнопка для попапа при просмотре документов
    private ButtonAppearance modal;

    /// Тема для точек
    private ButtonAppearance dots;

    /// Кнопка клавиатуры
    private ButtonAppearance keyboard;

    ...
}

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

MyDss.getAppearance().getButtons().getKeyboard().getAppearance().size = 20;

Персонализация фоновых элементов

Класс ViewAppearance позволяет задавать цвет фона:

public static class ViewAppearance {

    /// Цвет фона
    public int backgroundColor;

    ...
}

Следующие элементы интерфйеса заданы классом ViewAppearance и могут менять цвет фона:

public static class Views {

    /// Основной фон
    private ViewAppearance main;

    /// Фон камеры
    private ViewAppearance camera;

    /// Фон модального окна
    private ViewAppearance modal;

    /// Фон ячейки с предпросмотром документа
    private ViewAppearance documentPreview;

    /// Фон «выползающего» окна
    private ViewAppearance slideUpView;

    /// Цвет прогресс бара
    private ViewAppearance progressBar;

    /// Цвет поля для ввода
    private ViewAppearance editText;

    /// Цвет для фона картинки для отпечатка пальцев
    private ViewAppearance bgFingerprintPrompt;

    ...
}

Так, например, можно задать цвет фона полосы прогресса:

MyDss.getAppearance().getViews().getProgressBar().backgroundColor = R.color.dsssdk_color_white;

Персонализация панели инструментов

Внешний вид контроллера описывается следующими полями:

public static class ToolbarAppearance {

    // Оформление заголовка
    private LabelAppearance title;

    public LabelAppearance getTitle() {
        return title;
    }

    // Цвет фона
    public int toolbarTintColor;

    // Цвет элементов
    public int tintColor;

    ...
}

Внешний вид контроллера навигации задаётся следующим образом:

public static class BaseAppearance {

    /// Внешний вид контроллера основных экранов
    private ToolbarAppearance main;

    // Цвет статусбара
    public int statusBarColor;

    ...
}

Зададим, к примеру, цвет текста заголовка для контроллера основных экранов:

MyDss.getAppearance().getBaseAppearance().getMain().getTitle().color = R.color.dsssdk_color_white;

Инициализация SDK со списком доверенных приложений

При инициализации SDK функцией init класса MyDss выполняются проверки (с отображением соответствующих предупреждений) устройства на наличие прав суперпользователя (root), отсутствие антивируса, а также наличие приложений с правами android.permission.PACKAGE_USAGE_STATS и android.permission.SYSTEM_ALERT_WINDOW.

Если по вашему мнению SDK ошибочно определило некоторое приложение как вредоносное и показывает пользователю предупреждение, которое вы считаете избыточным, вы можете передать в функцию init список "доверенных" приложений в виде параметра HashMap<String, String[]> trustedApps, где ключом выступает идентификатор приложения, а значением - список хэшей (полученных по алгоритму SHA-256) подписей разработчика приложения.

В подавляющем большинстве случаев разработчики используют один ключ для подписания приложения перед публикацией в Play Market, поэтому чаще всего массив String[] будет содержать один элемент.

Получение подписи приложения и передача её в SDK

Рассмотрим алгоритм включения некоторого приложения в белый список приложений MyDSS SDK на примере приложения Google Photos.

Шаг 1. Установка приложения на устройство

Сначала необходимо установить из Play Market требуемое приложение на любой аппарат под управлением ОС Android, после чего подключить устройство к компьютеру по USB-кабелю в режиме отладки.

Шаг 2. Получение доступа к файлу apk приложения

Далее необходимо скопировать apk-архив из директории приложения на компьютер. Для этого необходимо использовать утилиту adb. При этом следует учесть, что к компьютеру не должно быть подключено по USB-кабелю других Android-устройств.

Утилита adb может быть установлена через Android Studio посредством инструмента SDK Manager. Подробнее на сайте разработчиков.

Команда

adb shell pm list packages

перечислит все установленные на подключённом устройстве приложения в виде пакетов. При желании можно отфильтровать полученный список удобным образом. В полученном списке будет строка package:com.google.android.apps.photos, где com.google.android.apps.photos - имя пакета (очевидно, соответствующее приложению Google Photos), которое понадобится для получения apk-файла, а также в качестве ключа в параметре trustedApps.

Выполнение команды

adb shell pm path com.google.android.apps.photos

позволяет получить пути ко всем apk-файлам приложения Google Photos. Результат выполнения команды будет примерно следующим:

package:/data/app/com.google.android.apps.photos-2/base.apk
package:/data/app/com.google.android.apps.photos-2/split_config.en.apk
package:/data/app/com.google.android.apps.photos-2/split_config.ru.apk
package:/data/app/com.google.android.apps.photos-2/split_config.uk.apk
package:/data/app/com.google.android.apps.photos-2/split_config.xxxhdpi.apk

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

adb pull /data/app/com.google.android.apps.photos-2/base.apk

Она скопирует base.apk в текущую директорию на вашем ПК.

Шаг 3. Извлечение подписи разработчика

Откройте apk-файл любым архиватором и перейдите в папку META-INF. Внутри этой папки будет один файл с расширением .RSA, именно он содержит подпись разработчика. Его необходимо разархивировать. Как правило, файл называется CERT.RSA или BNDLTOOL.RSA (иногда могут встречаться другие названия).

Далее необходимо прочитать содержимое файла. Сделать это можно, например, при помощи утилиты keytool, входящей в состав JRE.

keytool -printcert -file BNDLTOOL.RSA

Результат выполнения команды будет выглядеть примерно следующим образом:

Owner: CN=Unknown, OU="Google, Inc", O="Google, Inc", L=Mountain View, ST=CA, C=US
Issuer: CN=Unknown, OU="Google, Inc", O="Google, Inc", L=Mountain View, ST=CA, C=US
Serial number: 4934987e
Valid from: Tue Dec 02 05:07:58 MSK 2008 until: Sat Apr 19 05:07:58 MSK 2036
Certificate fingerprints:
         MD5:  D0:46:FC:5D:1F:C3:CD:0E:57:C5:44:40:97:CD:54:49
         SHA1: 24:BB:24:C0:5E:47:E0:AE:FA:68:A5:8A:76:61:79:D9:B6:13:A6:00
         SHA256: 3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A
Signature algorithm name: MD5withRSA (weak)
Subject Public Key Algorithm: 1024-bit RSA key
Version: 1

Нам необходим хэш, полученный по алгоритму SHA-256. Для этого копируем значение:

3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A

Шаг 4. Добавление приложения в SDK в качестве доверенного

После того, как у на есть App ID приложения и значение хэша подписи разработчика, мы можем проинициализировать SDK следующим образом:

String appId = "com.google.android.apps.photos";
String hash = "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A";
HashMap<String, String[]> trustedApps = new HashMap<>();
trustedApps.put(appId, new String[]{hash});
MyDss.init(
    activity.getApplicationContext(), 
    MyDss.RootCertificateType.Development, 
    MyDss.DSS_LOG_DEBUG, 
    trustedApps, 
    new DSSInitCallback() {
        @Override
        public void error(DSSError error) {
            // ...
        }

        @Override
        public void success(MyDss myDssInstance) {
            // ...
        }
    });

Альтернативный способ получения подписи разработчика приложения

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

Код получения подписей всех установленных приложений выглядит следующим образом:

private void getSHA256SignaturesForAllApps() throws NoSuchAlgorithmException {
    PackageManager p = getApplicationContext().getPackageManager();
    final List<PackageInfo> installedApps = p.getInstalledPackages(
            PackageManager.GET_PROVIDERS |
            (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
                    ? PackageManager.GET_SIGNING_CERTIFICATES
                    : PackageManager.GET_SIGNATURES));
    for (PackageInfo i : installedApps) {
        Log.w(TAG, "Package: "+i.packageName);
        Log.i(TAG, " -- name: "+p.getApplicationLabel(i.applicationInfo).toString());
        Signature[]  appSignatures;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
            appSignatures = i.signatures;
        } else {
            appSignatures = i.signingInfo.getSigningCertificateHistory();
        }
        for (Signature s: appSignatures) {
            String sigHex = bytesToHex(s.toByteArray());
            String sigSHA256Hex = bytesToHex(MessageDigest.getInstance("SHA-256").digest(s.toByteArray()));
            Log.i(TAG, " -- signature: "+sigHex);
            Log.i(TAG, " -- signature SHA256 digest: "+sigSHA256Hex);
        }
    }
}

private String bytesToHex(byte[] bytes) {
    // Представление байтов в hex-формате
    char[] hexArray = "0123456789abcdef".toCharArray();
    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0; j < bytes.length; j++) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars);
}