Система восстановления включает в себя несколько точек доступа для внедрения кода, специфичного для устройства, чтобы обновления по воздуху (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
- Блочное устройство eMMc в необработанном виде, используемое для загрузочных разделов, таких как загрузочный раздел и раздел восстановления. Подобно типу mtd, eMMc фактически никогда не монтируется, но строка точки монтирования используется для определения местоположения устройства в таблице.
- вати
- Файловая система FAT, установленная поверх блочного устройства, обычно используемого для внешних накопителей, таких как SD-карты. Устройство — это блочное устройство; device2 — это второе блочное устройство, которое система пытается смонтировать, если монтирование основного устройства не удается (для совместимости с SD-картами, которые могут быть отформатированы с таблицей разделов, а могут и не быть).
Все разделы должны быть смонтированы в корневой каталог (т.е. значение точки монтирования должно начинаться с косой черты и не содержать других косых черт). Это ограничение применяется только к монтированию файловых систем в режиме восстановления; основная система может монтировать их где угодно. Каталоги
/boot,/recoveryи/miscдолжны иметь тип raw (mtd или emmc), а каталоги/system,/data,/cacheи/sdcard(если доступны) должны иметь тип файловой системы (yaffs2, ext4 или vfat).
Начиная с Android 3.0, в файле recovery.fstab появляется дополнительное необязательное поле options . В настоящее время единственным определенным параметром является length , который позволяет явно указать длину раздела. Эта длина используется при переформатировании раздела (например, для раздела userdata во время операции очистки данных/сброса к заводским настройкам или для системного раздела во время установки полного OTA-пакета). Если значение length отрицательное, то размер для форматирования определяется путем добавления значения length к фактическому размеру раздела. Например, установка "length=-16384" означает, что последние 16 КБ этого раздела не будут перезаписаны при его переформатировании. Это поддерживает такие функции, как шифрование раздела userdata (где метаданные шифрования хранятся в конце раздела, которые не должны быть перезаписаны).
Примечание: Поля device2 и options являются необязательными, что может привести к неоднозначности при анализе. Если запись в четвертом поле строки начинается с символа '/', она считается записью device2 ; если запись не начинается с символа '/', она считается полем options .
Анимация загрузки
Производители устройств имеют возможность настраивать анимацию, отображаемую при загрузке Android-устройства. Для этого необходимо создать .zip-файл, организованный и расположенный в соответствии со спецификациями в формате bootanimation .
Для устройств Android Things вы можете загрузить заархивированный файл в консоль Android Things, чтобы изображения были включены в выбранный продукт.
Примечание: Эти изображения должны соответствовать фирменным стандартам Android. С фирменными стандартами можно ознакомиться в разделе Android в Центре партнерского маркетинга .
Интерфейс восстановления
Для поддержки устройств с различным доступным оборудованием (физические кнопки, светодиоды, экраны и т. д.) вы можете настроить интерфейс восстановления для отображения состояния и доступа к скрытым функциям, управляемым вручную, для каждого устройства.
Ваша задача — создать небольшую статическую библиотеку с парой объектов 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() . Подробную информацию об изображениях см. в разделе «Изображения пользовательского интерфейса восстановления» .
Класс устройства
После того, как у вас будет реализован 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() с действиями. Для массива элементов из примера с Тардисом используйте:
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 и более поздних версий используются два основных изображения: изображение ошибки и анимация установки .
Рисунок 1. icon_error.png | Рисунок 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 и более ранних версий используется изображение ошибки (показано выше), анимация установки , а также несколько наложенных изображений:
Рисунок 3. icon_installing.png | Рисунок 4. icon-installing_overlay01.png |
Рисунок 5. icon_installing_overlay07.png |
В процессе установки экранное изображение формируется путем отрисовки изображения icon_installing.png, а затем наложения одного из рамок поверх него с соответствующим смещением. Здесь красная рамка наложена поверх базового изображения, чтобы выделить место, где наложение размещено поверх базового изображения:
![]() Рисунок 6. Первый кадр анимации установки (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
Левый край заполняющего изображения отображается рядом с правым краем пустого изображения, образуя индикатор выполнения. Положение границы между двумя изображениями изменяется для отображения прогресса. Например, для указанных выше пар входных изображений отобразится:

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

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

Рисунок 13. Индикатор выполнения на отметке 50%.
Вы можете предоставить версии этих образов для конкретного устройства, поместив их (в этом примере) в папку device/yoyodyne/tardis/recovery/res/images . Имена файлов должны совпадать с указанными выше; если файл найден в этой директории, система сборки использует его вместо соответствующего образа по умолчанию. Поддерживаются только файлы PNG в формате RGB или RGBA с 8-битной глубиной цвета.
Примечание: В Android 5.x, если язык известен системе восстановления и является языком с письмом справа налево (арабский, иврит и т. д.), индикатор выполнения заполняется справа налево.
Устройства без экранов
Не все устройства 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"
Каждая функция расширения имеет одинаковую сигнатуру. Аргументами являются имя, под которым была вызвана функция, объект 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() для него.
Предположим, функции требуются два аргумента: строковый ключ и изображение в формате BLOB. Аргументы можно интерпретировать следующим образом:
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);
}Теперь вы можете настроить makefile для сборки статической библиотеки с вашим кодом. (Это тот же makefile, который использовался для настройки пользовательского интерфейса восстановления в предыдущем разделе; на вашем устройстве могут быть определены обе статические библиотеки.)
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, чтобы связать их с обновлением без вызова их (несуществующей) функции регистрации. Например, если ваш код, специфичный для устройства, хочет использовать 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() с одним аргументом, которая возвращает содержимое файла, извлеченного из пакета обновления, в виде двоичных данных (blob), для создания второго аргумента новой функции расширения.
генерация 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для исходных файлов target-files .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, а затем применять эти изменения к старым файлам target-files.
Теперь, при запуске ota_from_target_files , она автоматически распознает модуль, специфичный для устройства, из ZIP-архива target_files и использует его при генерации 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 Device::InvokeMenuItem() вашего устройства может возвращать следующие значения объекта BuiltinAction:
- APPLY_EXT . Sideload an update package from external storage (
/sdcarddirectory). Your recovery.fstab must define the/sdcardmount point. This is not usable on devices that emulate an SD card with a symlink to/data(or some similar mechanism)./datais typically not available to recovery because it may be encrypted. The recovery UI displays a menu of .zip files in/sdcardand allows the user to select one. - APPLY_CACHE . Similar to loading a package from
/sdcardexcept that the/cachedirectory (which is always available to recovery) is used instead. From the regular system,/cacheis only writable by privileged users, and if the device isn't bootable then the/cachedirectory 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.
A few caveats:
- 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
sideloadwork (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'sCheckKey()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.

