Код устройства

Система восстановления включает в себя несколько механизмов для вставки специфичного для устройства кода, чтобы обновления OTA могли также обновлять части устройства, отличные от системы Android (например, процессор базовой полосы или радиопроцессор).

В следующих разделах и примерах описывается настройка устройства TARDIS , произведенного поставщиком yoyodyne .

Карта разделов

Начиная с Android 2.3, платформа поддерживает флэш-накопители eMMc и файловую систему ext4, работающую на этих устройствах. Также поддерживаются флэш-накопители с технологией Memory Technology Device (MTD) и файловая система yaffs2 из более ранних версий.

Файл карты разделов задаётся параметром TARGET_RECOVERY_FSTAB; этот файл используется как исполняемым файлом восстановления, так и инструментами сборки пакетов. Имя файла карты можно указать в параметре TARGET_RECOVERY_FSTAB в файле BoardConfig.mk.

Пример файла карты разделов может выглядеть так:

device/yoyodyne/tardis/recovery.fstab
# mount point       fstype  device       [device2]        [options (3.0+ only)]

/sdcard     vfat    /dev/block/mmcblk0p1 /dev/block/mmcblk0
/cache      yaffs2  cache
/misc       mtd misc
/boot       mtd boot
/recovery   emmc    /dev/block/platform/s3c-sdhci.0/by-name/recovery
/system     ext4    /dev/block/platform/s3c-sdhci.0/by-name/system length=-4096
/data       ext4    /dev/block/platform/s3c-sdhci.0/by-name/userdata

За исключением /sdcard , которая необязательна, все точки монтирования в этом примере должны быть определены (устройства также могут добавлять дополнительные разделы). Поддерживаются пять типов файловых систем:

yaffs2
Файловая система yaffs2 на флэш-устройстве MTD. «device» должно быть именем раздела MTD и должно присутствовать в /proc/mtd .
мтд
Раздел MTD, используемый для загрузочных разделов, таких как разделы загрузки и восстановления. MTD фактически не монтируется, но точка монтирования используется в качестве ключа для поиска раздела. «device» должно быть именем раздела MTD в /proc/mtd .
ext4
Файловая система ext4 на флэш-устройстве eMMc. «device» должен быть путем к блочному устройству.
еммц
Блочное устройство eMMc, используемое для загрузочных разделов, таких как разделы загрузки и восстановления. Подобно типу mtd, eMMc фактически никогда не монтируется, но строка точки монтирования используется для поиска устройства в таблице.
vfat
Файловая система FAT, расположенная на блочном устройстве, обычно используемом для внешних накопителей, таких как SD-карты. Устройство (Device) — блочное устройство; устройство (Device2) — второе блочное устройство, которое система пытается смонтировать, если монтирование основного устройства не удаётся (для совместимости с SD-картами, которые могут быть отформатированы с использованием таблицы разделов или нет).

Все разделы должны быть смонтированы в корневом каталоге (т.е. значение точки монтирования должно начинаться со слеша и не содержать других слешей). Это ограничение применяется только к монтированию файловых систем в режиме восстановления; основная система может монтировать их где угодно. Каталоги /boot , /recovery и /misc должны иметь тип raw (mtd или emmc), а каталоги /system , /data , /cache и /sdcard (если доступно) должны иметь тип файловой системы (yaffs2, ext4 или vfat).

Начиная с Android 3.0, файл recovery.fstab получил дополнительное необязательное поле options . В настоящее время единственным определённым параметром является length , который позволяет явно указать длину раздела. Эта длина используется при переформатировании раздела (например, для раздела пользовательских данных во время операции стирания данных/сброса к заводским настройкам или для системного раздела во время установки полного пакета OTA). Если значение длины отрицательное, то размер для форматирования определяется путём сложения значения длины и истинного размера раздела. Например, установка «length=-16384» означает, что последние 16 КБ этого раздела не будут перезаписаны при переформатировании этого раздела. Это поддерживает такие функции, как шифрование раздела пользовательских данных (где метаданные шифрования хранятся в конце раздела, который не должен перезаписываться).

Примечание: Поля device2 и options необязательны, что создаёт неоднозначность при анализе. Если запись в четвёртом поле строки начинается с символа «/», она считается записью device2 ; если запись не начинается с символа «/», она считается полем options .

Анимация загрузки

Производители устройств могут настраивать анимацию, отображаемую при загрузке Android-устройства. Для этого создайте ZIP-файл, организованный и размещенный в соответствии со спецификациями в формате bootanimation .

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

Примечание: эти изображения должны соответствовать правилам бренда Android. Правила бренда см. в разделе Android на сайте Partner Marketing Hub .

Интерфейс восстановления

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

Ваша цель — создать небольшую статическую библиотеку с парой объектов C++ для реализации функциональности, специфичной для устройства. Файл bootable/recovery/default_device.cpp используется по умолчанию и может стать хорошей отправной точкой для копирования при написании версии этого файла для вашего устройства.

Примечание: здесь может появиться сообщение « Нет команды» . Чтобы переключить текст, удерживайте кнопку питания и одновременно нажмите кнопку увеличения громкости. Если на вашем устройстве нет обеих кнопок, для переключения текста нажмите и удерживайте любую кнопку.

device/yoyodyne/tardis/recovery/recovery_ui.cpp
#include <linux/input.h>

#include "common.h"
#include "device.h"
#include "screen_ui.h"

Функции заголовков и элементов

Классу Device требуются функции для возврата заголовков и элементов, отображаемых в скрытом меню восстановления. Заголовки описывают, как работать с меню (например, элементы управления для изменения/выбора выделенного элемента).

static const char* HEADERS[] = { "Volume up/down to move highlight;",
                                 "power button to select.",
                                 "",
                                 NULL };

static const char* ITEMS[] =  {"reboot system now",
                               "apply update from ADB",
                               "wipe data/factory reset",
                               "wipe cache partition",
                               NULL };

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

Настроить CheckKey

Затем определите реализацию RecoveryUI вашего устройства. В этом примере предполагается, что у устройства TARDIS есть экран, поэтому вы можете наследовать от встроенной реализации ScreenRecoveryUI (см. инструкции для устройств без экрана ). Единственная функция ScreenRecoveryUI, которую можно настроить, — это CheckKey() , которая выполняет начальную асинхронную обработку ключей:

class TardisUI : public ScreenRecoveryUI {
  public:
    virtual KeyAction CheckKey(int key) {
        if (key == KEY_HOME) {
            return TOGGLE;
        }
        return ENQUEUE;
    }
};

КЛЮЧЕВЫЕ константы

Константы KEY_* определены в linux/input.h . CheckKey() вызывается независимо от того, что происходит в процессе восстановления: при отключении меню, при его включении, во время установки пакета, во время очистки пользовательских данных и т. д. Функция может возвращать одну из четырёх констант:

  • ВКЛ ./ВЫКЛ. Включает/выключает отображение меню и/или текстового журнала.
  • ПЕРЕЗАГРУЗКА . Немедленно перезагрузите устройство.
  • ИГНОРИРОВАТЬ . Игнорировать это нажатие клавиши.
  • ENQUEUE . Поставить это нажатие клавиши в очередь для синхронного использования (т.е. системой меню восстановления, если дисплей включён).

CheckKey() вызывается каждый раз, когда за событием нажатия клавиши следует событие отпускания той же клавиши. (Последовательность событий A-нажатие B-нажатие B-поднятие A-поднятие приводит только к вызову CheckKey(B) .) CheckKey() может вызвать IsKeyPressed() , чтобы узнать, удерживаются ли другие клавиши. (В указанной выше последовательности событий нажатия клавиш, если бы CheckKey(B) вызывал IsKeyPressed(A) он бы вернул значение true.)

CheckKey() может сохранять состояние в своём классе; это может быть полезно для определения последовательностей нажатий клавиш. В этом примере показана немного более сложная конфигурация: дисплей переключается удержанием кнопки питания и нажатием кнопки увеличения громкости, а устройство можно немедленно перезагрузить, нажав кнопку питания пять раз подряд (без нажатия других клавиш):

class TardisUI : public ScreenRecoveryUI {
  private:
    int consecutive_power_keys;

  public:
    TardisUI() : consecutive_power_keys(0) {}

    virtual KeyAction CheckKey(int key) {
        if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
            return TOGGLE;
        }
        if (key == KEY_POWER) {
            ++consecutive_power_keys;
            if (consecutive_power_keys >= 5) {
                return REBOOT;
            }
        } else {
            consecutive_power_keys = 0;
        }
        return ENQUEUE;
    }
};

ScreenRecoveryUI

При использовании собственных изображений (значок ошибки, анимация установки, индикаторы выполнения) с ScreenRecoveryUI вы можете задать переменную animation_fps для управления скоростью анимации в кадрах в секунду (FPS).

Примечание: Текущий скрипт interlace-frames.py позволяет сохранять информацию animation_fps в самом изображении. В более ранних версиях Android приходилось устанавливать animation_fps самостоятельно.

Чтобы задать переменную animation_fps , переопределите функцию ScreenRecoveryUI::Init() в подклассе. Задайте значение, а затем вызовите parent Init() для завершения инициализации. Значение по умолчанию (20 FPS) соответствует изображениям восстановления по умолчанию; при использовании этих изображений функция Init() не требуется. Подробнее об изображениях см. в разделе «Изображения UI восстановления» .

Класс устройства

После реализации RecoveryUI определите класс устройства (подкласс встроенного класса Device). Он должен создать единственный экземпляр класса UI и вернуть его из функции GetUI() :

class TardisDevice : public Device {
  private:
    TardisUI* ui;

  public:
    TardisDevice() :
        ui(new TardisUI) {
    }

    RecoveryUI* GetUI() { return ui; }

StartRecovery

Метод StartRecovery() вызывается в начале процесса восстановления, после инициализации пользовательского интерфейса и анализа аргументов, но до выполнения каких-либо действий. Реализация по умолчанию ничего не делает, поэтому вам не нужно предоставлять его в подклассе, если вам нечего делать:

   void StartRecovery() {
       // ... do something tardis-specific here, if needed ....
    }

Поставка и управление меню восстановления

Система вызывает два метода для получения списка строк заголовков и списка элементов. В этой реализации она возвращает статические массивы, определённые в начале файла:

const char* const* GetMenuHeaders() { return HEADERS; }
const char* const* GetMenuItems() { return ITEMS; }

HandleMenuKey

Затем предоставьте функцию HandleMenuKey() , которая принимает нажатие клавиши и текущую видимость меню, а затем решает, какое действие предпринять:

   int HandleMenuKey(int key, int visible) {
        if (visible) {
            switch (key) {
              case KEY_VOLUMEDOWN: return kHighlightDown;
              case KEY_VOLUMEUP:   return kHighlightUp;
              case KEY_POWER:      return kInvokeItem;
            }
        }
        return kNoAction;
    }

Метод принимает код клавиши (который был ранее обработан и добавлен в очередь методом CheckKey() объекта пользовательского интерфейса) и текущее состояние видимости меню/текстового журнала. Возвращаемое значение — целое число. Если значение равно 0 или больше, оно принимается за позицию пункта меню, который вызывается немедленно (см. метод InvokeMenuItem() ниже). В противном случае это может быть одна из следующих предопределенных констант:

  • kHighlightUp . Переместить выделение меню на предыдущий пункт.
  • kHighlightDown . Переместить выделение меню на следующий пункт.
  • kInvokeItem . Вызвать текущий выделенный элемент.
  • kNoAction . Ничего не делать при нажатии этой клавиши.

Как следует из аргумента visible, HandleMenuKey() вызывается, даже если меню не отображается. В отличие от CheckKey() , он не вызывается, когда процесс восстановления выполняет какие-либо действия, например, очистку данных или установку пакета, — он вызывается только тогда, когда процесс восстановления находится в режиме ожидания ввода.

Механизмы трекбола

Если ваше устройство оснащено механизмом ввода, подобным трекболу (генерирует события ввода с типом EV_REL и кодом REL_Y), функция восстановления синтезирует нажатия клавиш KEY_UP и KEY_DOWN всякий раз, когда устройство ввода, подобное трекболу, регистрирует движение по оси Y. Всё, что вам нужно сделать, — это сопоставить события KEY_UP и KEY_DOWN с действиями в меню. Для CheckKey() это сопоставление не выполняется, поэтому вы не можете использовать движения трекбола в качестве триггеров для перезагрузки или переключения дисплея.

Клавиши-модификаторы

Чтобы проверить, удерживаются ли клавиши-модификаторы, вызовите метод IsKeyPressed() вашего объекта пользовательского интерфейса. Например, на некоторых устройствах нажатие Alt-W в режиме восстановления запускает очистку данных независимо от того, отображается меню или нет. Реализовать это можно следующим образом:

   int HandleMenuKey(int key, int visible) {
        if (ui->IsKeyPressed(KEY_LEFTALT) && key == KEY_W) {
            return 2;  // position of the "wipe data" item in the menu
        }
        ...
    }

Примечание: Если visible равно false, возвращать специальные значения, управляющие меню (перемещают выделение, вызывают выделенный элемент), не имеет смысла, поскольку пользователь не видит выделение. Однако при желании вы можете вернуть эти значения.

InvokeMenuItem

Затем предоставьте метод InvokeMenuItem() , который сопоставляет целочисленные позиции в массиве элементов, возвращаемых GetMenuItems() с действиями. Для массива элементов в примере с Tardis используйте:

   BuiltinAction InvokeMenuItem(int menu_position) {
        switch (menu_position) {
          case 0: return REBOOT;
          case 1: return APPLY_ADB_SIDELOAD;
          case 2: return WIPE_DATA;
          case 3: return WIPE_CACHE;
          default: return NO_ACTION;
        }
    }

Этот метод может вернуть любой элемент перечисления BuiltinAction, чтобы сообщить системе о необходимости выполнения этого действия (или элемент NO_ACTION, если вы хотите, чтобы система не выполняла никаких действий). Здесь можно предоставить дополнительные функции восстановления, выходящие за рамки системных: добавьте для него пункт в меню, выполняйте его здесь при вызове этого пункта меню и возвращайте NO_ACTION, чтобы система не выполняла никаких других действий.

BuiltinAction содержит следующие значения:

  • NO_ACTION . Ничего не делать.
  • ПЕРЕЗАГРУЗКА . Выйдите из режима восстановления и перезагрузите устройство в обычном режиме.
  • APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD . Установка пакета обновления из разных источников. Подробнее см. в разделе «Загрузка неопубликованных приложений» .
  • WIPE_CACHE . Переформатируйте только раздел кэша. Подтверждение не требуется, так как это относительно безвредно.
  • WIPE_DATA . Переформатируйте разделы пользовательских данных и кэша, также известные как сброс настроек к заводским. Перед продолжением пользователю будет предложено подтвердить это действие.

Последний метод, WipeData() , необязателен и вызывается при каждом запуске операции очистки данных (либо при восстановлении через меню, либо при сбросе настроек к заводским из основной системы). Этот метод вызывается перед очисткой разделов пользовательских данных и кэша. Если ваше устройство хранит пользовательские данные где-либо, кроме этих двух разделов, необходимо очистить их здесь. Для подтверждения успешного завершения следует вернуть 0, а в случае неудачи — другое значение, хотя в настоящее время возвращаемое значение игнорируется. Разделы пользовательских данных и кэша очищаются независимо от того, вернули ли вы успешную или неудачную операцию.

   int WipeData() {
       // ... do something tardis-specific here, if needed ....
       return 0;
    }

Сделать устройство

Наконец, включите в конец файла recovery_ui.cpp некоторый шаблонный код для функции make_device() , которая создает и возвращает экземпляр вашего класса Device:

class TardisDevice : public Device {
   // ... all the above methods ...
};

Device* make_device() {
    return new TardisDevice();
}

После завершения создания файла recovery_ui.cpp скомпилируйте его и подключите к системе восстановления на вашем устройстве. В Android.mk создайте статическую библиотеку, содержащую только этот файл C++:

device/yoyodyne/tardis/recovery/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES += bootable/recovery
LOCAL_SRC_FILES := recovery_ui.cpp

# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
LOCAL_MODULE := librecovery_ui_tardis

include $(BUILD_STATIC_LIBRARY)

Затем в конфигурации платы для этого устройства укажите вашу статическую библиотеку в качестве значения TARGET_RECOVERY_UI_LIB.

device/yoyodyne/tardis/BoardConfig.mk
 [...]

# device-specific extensions to the recovery UI
TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis

Изображения пользовательского интерфейса восстановления

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

Интерфейс, состоящий только из изображений, устраняет необходимость локализации. Однако, начиная с Android 5.0, обновление может отображать текстовую строку (например, «Установка обновления системы...») вместе с изображением. Подробнее см. в разделе Локализованный текст восстановления .

Android 5.0 и более поздние версии

В пользовательском интерфейсе восстановления Android 5.0 и более поздних версий используются два основных изображения: изображение ошибки и анимация установки .

изображение, показанное во время ошибки ota

Рисунок 1. icon_error.png

изображение, показанное во время установки OTA

Рисунок 2. icon_installing.png

Анимация установки представлена ​​в виде одного PNG-изображения с чередующимися построчно кадрами анимации (именно поэтому рисунок 2 выглядит сплющенным). Например, для семикадровой анимации размером 200x200 создайте одно изображение размером 200x1400, где первый кадр содержит строки 0, 7, 14, 21, ...; второй кадр — строки 1, 8, 15, 22, ... и т.д. Объединенное изображение включает текстовый фрагмент, указывающий количество кадров анимации и частоту кадров в секунду (FPS). Утилита bootable/recovery/interlace-frames.py принимает набор входных кадров и объединяет их в необходимое составное изображение, используемое для восстановления.

Изображения по умолчанию доступны в различных разрешениях и находятся в папке bootable/recovery/res-$DENSITY/images (например, bootable/recovery/res-hdpi/images ). Чтобы использовать статическое изображение во время установки, достаточно указать изображение icon_installing.png и установить количество кадров анимации равным 0 (значок ошибки не анимирован, он всегда представляет собой статичное изображение).

Android 4.x и более ранние версии

Пользовательский интерфейс восстановления Android 4.x и более ранних версий использует изображение ошибки (показано выше) и анимацию установки , а также несколько наложенных изображений:

изображение, показанное во время установки OTA

Рисунок 3. icon_installing.png

изображение показано как первое наложение

Рисунок 4. icon-installing_overlay01.png

изображение показано как седьмое наложение

Рисунок 5. icon_installing_overlay07.png

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

составное изображение установки плюс первый оверлей

Рисунок 6. Установка кадра анимации 1 (icon_installing.png + icon_installing_overlay01.png)

составное изображение установки плюс седьмой оверлей

Рисунок 7. Установка кадра анимации 7 (icon_installing.png + icon_installing_overlay07.png)

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

Количество кадров анимации, желаемая скорость и смещения оверлея по осям X и Y относительно базового изображения задаются переменными-членами класса ScreenRecoveryUI. При использовании пользовательских изображений вместо изображений по умолчанию переопределите метод Init() в подклассе, чтобы изменить эти значения для ваших пользовательских изображений (подробнее см. в разделе ScreenRecoveryUI ). Скрипт bootable/recovery/make-overlay.py может помочь преобразовать набор кадров изображения в форму «базовое изображение + изображения оверлея», необходимую для восстановления, включая вычисление необходимых смещений.

Изображения по умолчанию находятся в bootable/recovery/res/images . Чтобы использовать статическое изображение во время установки, достаточно предоставить изображение icon_installing.png и установить количество кадров анимации равным 0 (значок ошибки не анимирован, а всегда представляет собой статичное изображение).

Локализованный текст восстановления

Android 5.x отображает текстовую строку (например, «Установка обновления системы...») вместе с изображением. При загрузке основной системы в режим восстановления текущая локаль пользователя передается в режим восстановления в качестве параметра командной строки. Для каждого отображаемого сообщения режим восстановления добавляет второе составное изображение с предварительно отрендеренными текстовыми строками для этого сообщения в каждом регионе.

Пример изображения текстовых строк восстановления:

изображение текста восстановления

Рисунок 8. Локализованный текст для сообщений о восстановлении

Текст восстановления может отображать следующие сообщения:

  • Установка обновления системы...
  • Ошибка!
  • Удаление... (при выполнении стирания данных/сброса настроек к заводским)
  • Нет команды (когда пользователь загружает восстановление вручную)

Приложение для Android в bootable/recovery/tools/recovery_l10n/ отображает локализации сообщения и создаёт составное изображение. Подробную информацию об использовании этого приложения см. в комментариях к bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java .

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

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

Индикаторы прогресса

Под основным изображением (или анимацией) может отображаться индикатор выполнения. Он создается путем объединения двух исходных изображений, которые должны быть одинакового размера:

пустой индикатор выполнения

Рисунок 9. progress_empty.png

полный индикатор прогресса

Рисунок 10. progress_fill.png

Левый край заполняющего изображения отображается рядом с правым краем пустого изображения, образуя индикатор выполнения. Положение границы между двумя изображениями изменяется для отображения хода выполнения. Например, для приведенных выше пар входных изображений отобразите:

полоса прогресса на 1%

Рисунок 11. Индикатор выполнения на 1%>

полоса прогресса на 10%

Рисунок 12. Индикатор выполнения на 10%

полоса прогресса на 50%

Рисунок 13. Полоса прогресса на 50%

Вы можете предоставить версии этих изображений, специфичные для конкретного устройства, поместив их (в данном примере) в папку device/yoyodyne/tardis/recovery/res/images . Имена файлов должны соответствовать указанным выше; при обнаружении файла в этом каталоге система сборки отдаст ему приоритет перед соответствующим изображением по умолчанию. Поддерживаются только PNG-файлы в формате RGB или RGBA с 8-битной глубиной цвета.

Примечание: в Android 5.x, если локаль известна для восстановления и является языком с письмом справа налево (RTL) (арабский, иврит и т. д.), полоса прогресса заполняется справа налево.

Устройства без экранов

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

RecoveryUI содержит методы для обработки низкоуровневых операций пользовательского интерфейса, таких как «включить/выключить дисплей», «обновить индикатор выполнения», «отобразить меню», «изменить пункт меню» и т. д. Вы можете переопределить их, чтобы обеспечить подходящий интерфейс для вашего устройства. Возможно, ваше устройство оснащено светодиодами, которые можно использовать для индикации состояния разными цветами или схемами мигания, или, может быть, вы можете воспроизводить звук. (Возможно, вы вообще не хотите поддерживать меню или режим «отображения текста»; вы можете запретить доступ к ним с помощью реализаций CheckKey() и HandleMenuKey() , которые никогда не включают дисплей и не выбирают пункт меню. В этом случае многие из необходимых методов RecoveryUI могут быть просто пустыми заглушками.)

Чтобы узнать, какие методы необходимо поддерживать, см. файл bootable/recovery/ui.h для объявления RecoveryUI. RecoveryUI является абстрактным — некоторые методы являются чисто виртуальными и должны предоставляться подклассами, — но он содержит код для обработки нажатия клавиш. Вы также можете переопределить его, если на вашем устройстве нет клавиш или вы хотите обрабатывать их по-другому.

Обновление

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

device/yoyodyne/tardis/recovery/recovery_updater.c
#include <stdlib.h>
#include <string.h>

#include "edify/expr.h"

Каждая функция расширения имеет одинаковую сигнатуру. Аргументами являются имя, под которым была вызвана функция, cookie-файл State* , количество входящих аргументов и массив указателей Expr* , представляющих аргументы. Возвращаемое значение — вновь выделенное Value* .

Value* ReprogramTardisFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc != 2) {
        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
    }

Ваши аргументы не были вычислены на момент вызова функции — логика вашей функции определяет, какие из них будут вычислены и сколько раз. Таким образом, вы можете использовать функции расширения для реализации собственных управляющих структур. Call Evaluate() для вычисления аргумента Expr* , возвращающего Value* . Если Evaluate() возвращает NULL, необходимо освободить все удерживаемые ресурсы и немедленно вернуть NULL (это распространяет аварийные завершения по стеку edify). В противном случае вы становитесь владельцем возвращаемого Value и отвечаете за последующий вызов FreeValue() .

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

   Value* key = EvaluateValue(state, argv[0]);
    if (key == NULL) {
        return NULL;
    }
    if (key->type != VAL_STRING) {
        ErrorAbort(state, "first arg to %s() must be string", name);
        FreeValue(key);
        return NULL;
    }
    Value* image = EvaluateValue(state, argv[1]);
    if (image == NULL) {
        FreeValue(key);    // must always free Value objects
        return NULL;
    }
    if (image->type != VAL_BLOB) {
        ErrorAbort(state, "second arg to %s() must be blob", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

Проверка на значение NULL и освобождение ранее вычисленных аргументов может быть утомительной при наличии нескольких аргументов. Функция ReadValueArgs() может упростить эту задачу. Вместо кода выше можно было бы написать так:

   Value* key;
    Value* image;
    if (ReadValueArgs(state, argv, 2, &key, &image) != 0) {
        return NULL;     // ReadValueArgs() will have set the error message
    }
    if (key->type != VAL_STRING || image->type != VAL_BLOB) {
        ErrorAbort(state, "arguments to %s() have wrong type", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

ReadValueArgs() не выполняет проверку типов, поэтому её необходимо выполнить здесь. Удобнее сделать это одним оператором if, но при этом в случае сбоя выдаётся несколько менее конкретное сообщение об ошибке. Однако ReadValueArgs() обрабатывает каждый аргумент и освобождает все ранее вычисленные аргументы (а также выдаёт полезное сообщение об ошибке), если какое-либо из вычислений завершается неудачей. Для вычисления переменного количества аргументов можно использовать вспомогательную функцию ReadValueVarArgs() (она возвращает массив Value* ).

После оценки аргументов выполните работу функции:

   // key->data is a NUL-terminated string
    // image->data and image->size define a block of binary data
    //
    // ... some device-specific magic here to
    // reprogram the tardis using those two values ...

Возвращаемое значение должно быть объектом Value* ; право собственности на этот объект перейдет к вызывающему объекту. Вызывающий объект становится владельцем всех данных, на которые указывает этот Value* , в частности, элемента данных.

В этом случае вы хотите вернуть значение true или false, чтобы обозначить успешное выполнение. Помните о соглашении, что пустая строка — false , а все остальные строки — true . Необходимо выделить объект Value с выделенной копией константной строки, которую нужно вернуть, так как вызывающий код free() . Не забудьте вызвать FreeValue() для объектов, полученных в результате вычисления аргументов!

   FreeValue(key);
    FreeValue(image);

    Value* result = malloc(sizeof(Value));
    result->type = VAL_STRING;
    result->data = strdup(successful ? "t" : "");
    result->size = strlen(result->data);
    return result;
}

Удобная функция StringValue() оборачивает строку в новый объект Value. Используйте её, чтобы записать код выше более кратко:

   FreeValue(key);
    FreeValue(image);

    return StringValue(strdup(successful ? "t" : ""));
}

Чтобы подключить функции к интерпретатору edify, предоставьте функцию Register_ foo , где foo — имя статической библиотеки, содержащей этот код. Вызовите RegisterFunction() для регистрации каждой функции расширения. По соглашению, функции, специфичные для устройств, следует называть device . whatever чтобы избежать конфликтов с будущими встроенными функциями.

void Register_librecovery_updater_tardis() {
    RegisterFunction("tardis.reprogram", ReprogramTardisFn);
}

Теперь вы можете настроить make-файл для создания статической библиотеки с вашим кодом. (Это тот же make-файл, который использовался для настройки пользовательского интерфейса восстановления в предыдущем разделе; на вашем устройстве могут быть определены обе статические библиотеки.)

device/yoyodyne/tardis/recovery/Android.mk
include $(CLEAR_VARS)
LOCAL_SRC_FILES := recovery_updater.c
LOCAL_C_INCLUDES += bootable/recovery

Имя статической библиотеки должно совпадать с именем содержащейся в ней функции Register_ libname .

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Наконец, настройте сборку восстановления для загрузки вашей библиотеки. Добавьте вашу библиотеку в TARGET_RECOVERY_UPDATER_LIBS (который может содержать несколько библиотек; все они регистрируются). Если ваш код зависит от других статических библиотек, которые сами по себе не являются расширениями edify (т.е. у них нет функции Register_ libname ), вы можете перечислить их в TARGET_RECOVERY_UPDATER_EXTRA_LIBS, чтобы связать их с updater без вызова их (несуществующей) функции регистрации. Например, если ваш код, специфичный для устройства, хочет использовать zlib для распаковки данных, необходимо включить libz здесь.

device/yoyodyne/tardis/BoardConfig.mk
 [...]

# add device-specific extensions to the updater binary
TARGET_RECOVERY_UPDATER_LIBS += librecovery_updater_tardis
TARGET_RECOVERY_UPDATER_EXTRA_LIBS +=

Скрипты обновления в вашем пакете OTA теперь могут вызывать вашу функцию как любую другую. Для перепрограммирования устройства TARDIS скрипт обновления может содержать: tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) . Здесь используется одноаргументная версия встроенной функции package_extract_file() , которая возвращает содержимое файла, извлечённого из пакета обновления, в виде двоичного двоичного объекта для создания второго аргумента новой функции расширения.

Генерация OTA-пакетов

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

Сначала сообщите системе сборки о блоке данных, специфичном для устройства. Предполагая, что ваш файл данных находится в файле device/yoyodyne/tardis/tardis.dat , объявите следующее в файле AndroidBoard.mk вашего устройства:

device/yoyodyne/tardis/AndroidBoard.mk
  [...]

$(call add-radio-file,tardis.dat)

Вы также можете поместить его в Android.mk, но тогда его необходимо будет защитить проверкой устройства, поскольку все файлы Android.mk в дереве загружаются независимо от того, какое устройство собирается. (Если ваше дерево включает несколько устройств, вам нужно добавить только файл tardis.dat при сборке устройства tardis.)

device/yoyodyne/tardis/Android.mk
  [...]

# an alternative to specifying it in AndroidBoard.mk
ifeq (($TARGET_DEVICE),tardis)
  $(call add-radio-file,tardis.dat)
endif

Они называются радиофайлами по историческим причинам; они могут не иметь никакого отношения к радиоустройству устройства (если оно есть). Это просто непрозрачные блоки данных, которые система сборки копирует в архив target-files.zip, используемый инструментами генерации OTA. При сборке файл tardis.dat сохраняется в архиве target-files.zip под именем RADIO/tardis.dat . Вы можете вызывать add-radio-file несколько раз, чтобы добавить любое количество файлов.

Модуль Python

Чтобы расширить возможности инструментов выпуска, напишите модуль Python (с именем releasetools.py), который инструменты смогут вызывать, если он есть. Пример:

device/yoyodyne/tardis/releasetools.py
import common

def FullOTA_InstallEnd(info):
  # copy the data into the package.
  tardis_dat = info.input_zip.read("RADIO/tardis.dat")
  common.ZipWriteStr(info.output_zip, "tardis.dat", tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Отдельная функция обрабатывает случай генерации инкрементального OTA-пакета. В этом примере предположим, что вам нужно перепрограммировать TARDIS только в том случае, если файл tardis.dat изменился между двумя сборками.

def IncrementalOTA_InstallEnd(info):
  # copy the data into the package.
  source_tardis_dat = info.source_zip.read("RADIO/tardis.dat")
  target_tardis_dat = info.target_zip.read("RADIO/tardis.dat")

  if source_tardis_dat == target_tardis_dat:
      # tardis.dat is unchanged from previous build; no
      # need to reprogram it
      return

  # include the new tardis.dat in the OTA package
  common.ZipWriteStr(info.output_zip, "tardis.dat", target_tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Функции модуля

В модуле вы можете реализовать следующие функции (реализуйте только те, которые вам необходимы).

FullOTA_Assertions()
Вызывается в начале генерации полного OTA. Это хорошее место для выдачи утверждений о текущем состоянии устройства. Не выдавайте команды скрипта, которые вносят изменения в устройство.
FullOTA_InstallBegin()
Вызывается после выполнения всех проверок состояния устройства, но до внесения каких-либо изменений. Вы можете отправлять команды для обновления, специфичного для устройства, которые должны быть выполнены до внесения каких-либо изменений на устройстве.
FullOTA_InstallEnd()
Вызывается в конце генерации скрипта, после выполнения команд скрипта для обновления загрузочного и системного разделов. Также можно выполнить дополнительные команды для обновления, специфичного для устройства.
IncrementalOTA_Assertions()
Аналогично FullOTA_Assertions() , но вызывается при создании пакета инкрементного обновления.
IncrementalOTA_VerifyBegin()
Вызывается после выполнения всех проверок состояния устройства, но до внесения каких-либо изменений. Вы можете отправлять команды для обновления, специфичного для устройства, которые должны быть выполнены до внесения каких-либо изменений на устройстве.
IncrementalOTA_VerifyEnd()
Вызывается в конце фазы проверки, когда скрипт завершает проверку файлов, к которым он будет обращаться, на наличие ожидаемого начального содержимого. На этом этапе на устройстве ничего не изменилось. Вы также можете выдать код для дополнительных проверок, специфичных для устройства.
IncrementalOTA_InstallBegin()
Вызывается после того, как файлы, подлежащие исправлению, проверены на соответствие ожидаемому состоянию до внесения изменений, но до внесения каких-либо изменений. Вы можете отправлять команды для обновлений, специфичных для устройства, которые должны быть выполнены до внесения каких-либо изменений на устройстве.
IncrementalOTA_InstallEnd()
Подобно полному пакету OTA, этот метод вызывается в конце генерации скрипта, после отправки команд скрипта для обновления загрузочного и системного разделов. Вы также можете отправить дополнительные команды для обновлений, специфичных для конкретного устройства.

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

Передача функций информационным объектам

Передайте функции одному информационному объекту, содержащему различные полезные элементы:

  • info.input_zip . (Только для полных OTA) Объект zipfile.ZipFile для входных целевых файлов .zip.
  • info.source_zip . (Только для инкрементных OTA) Объект zipfile.ZipFile для исходных целевых файлов .zip (сборка уже находится на устройстве на момент установки инкрементного пакета).
  • info.target_zip . (Только для инкрементных OTA) Объект zipfile.ZipFile для целевых файлов target-files .zip (сборка, которую инкрементный пакет помещает на устройство).
  • info.output_zip . Пакет создаётся; объект zipfile.ZipFile открыт для записи. Используйте common.ZipWriteStr(info.output_zip, filename , data ), чтобы добавить файл в пакет.
  • info.script . Объект скрипта, к которому можно добавлять команды. Вызовите info.script.AppendExtra( script_text ) для вывода текста в скрипт. Убедитесь, что выводимый текст заканчивается точкой с запятой, чтобы он не столкнулся с командами, выполняемыми после него.

Подробную информацию об объекте info см. в документации Python Software Foundation по архивам ZIP .

Укажите местоположение модуля

Укажите расположение скрипта releasetools.py вашего устройства в файле BoardConfig.mk:

device/yoyodyne/tardis/BoardConfig.mk
 [...]

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

Если TARGET_RELEASETOOLS_EXTENSIONS не задан, по умолчанию используется каталог $(TARGET_DEVICE_DIR)/../common (в данном примере device/yoyodyne/common ). Лучше всего явно указать расположение скрипта releasetools.py. При сборке устройства TARDIS скрипт releasetools.py включается в ZIP-файл target-files ( META/releasetools.py ).

При запуске инструментов выпуска ( img_from_target_files или ota_from_target_files ) скрипт releasetools.py из архива target-files.zip (если он есть) имеет приоритет над скриптом из исходного дерева Android. Вы также можете явно указать путь к расширениям, специфичным для устройства, с помощью параметра -s (или --device_specific ), который имеет наивысший приоритет. Это позволяет исправлять ошибки и вносить изменения в расширения releasetools, а также применять эти изменения к старым целевым файлам.

Теперь при запуске ota_from_target_files он автоматически выбирает специфичный для устройства модуль из файла target_files .zip и использует его при генерации пакетов OTA:

./build/make/tools/releasetools/ota_from_target_files \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

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

./build/make/tools/releasetools/ota_from_target_files \
    -s device/yoyodyne/tardis \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

Примечание: полный список параметров см. в комментариях ota_from_target_files в build/make/tools/releasetools/ota_from_target_files .

Механизм боковой загрузки

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

Традиционно загрузка сторонних приложений осуществлялась путём загрузки пакетов с SD-карты устройства. Если устройство не загружается, пакет можно записать на SD-карту с помощью другого компьютера, а затем вставить SD-карту в устройство. Для устройств Android без съёмного внешнего накопителя, функция восстановления поддерживает два дополнительных механизма загрузки сторонних приложений: загрузка пакетов из кэш-раздела и загрузка их через USB с помощью adb.

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

  • APPLY_EXT . Sideload an update package from external storage ( /sdcard directory). Your recovery.fstab must define the /sdcard mount point. This is not usable on devices that emulate an SD card with a symlink to /data (or some similar mechanism). /data is typically not available to recovery because it may be encrypted. The recovery UI displays a menu of .zip files in /sdcard and allows the user to select one.
  • APPLY_CACHE . Similar to loading a package from /sdcard except that the /cache directory (which is always available to recovery) is used instead. From the regular system, /cache is only writable by privileged users, and if the device isn't bootable then the /cache directory can't be written to at all (which makes this mechanism of limited utility).
  • APPLY_ADB_SIDELOAD . Allows user to send a package to the device via a USB cable and the adb development tool. When this mechanism is invoked, recovery starts up its own mini version of the adbd daemon to let adb on a connected host computer talk to it. This mini version supports only a single command: adb sideload filename . The named file is sent from the host machine to the device, which then verifies and installs it just as if it had been on local storage.

Несколько предостережений:

  • Only USB transport is supported.
  • If your recovery runs adbd normally (usually true for userdebug and eng builds), that will be shut down while the device is in adb sideload mode and will be restarted when adb sideload has finished receiving a package. While in adb sideload mode, no adb commands other than sideload work ( logcat , reboot , push , pull , shell , etc. all fail).
  • You cannot exit adb sideload mode on the device. To abort, you can send /dev/null (or anything else that's not a valid package) as the package, and then the device will fail to verify it and stop the installation procedure. The RecoveryUI implementation's CheckKey() method will continue to be called for keypresses, so you can provide a key sequence that reboots the device and works in adb sideload mode.