Система восстановления включает в себя несколько хуков для вставки специфичного для устройства кода, чтобы обновления 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 без содержимого, используемый для загрузочных разделов, таких как boot и recovery. MTD на самом деле не монтируется, но точка монтирования используется как ключ для поиска раздела. "device" должно быть именем раздела MTD в
/proc/mtd
. - ext4
- Файловая система ext4 на флэш-устройстве eMMc. «device» должен быть путем к блочному устройству.
- еммк
- Необработанное блочное устройство eMMc, используемое для загрузочных разделов, таких как boot и recovery. Подобно типу 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 , который позволяет явно указать длину раздела. Эта длина используется при переформатировании раздела (например, для раздела пользовательских данных во время операции стирания данных/сброса к заводским настройкам или для системного раздела во время установки полного пакета 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
используется по умолчанию и является хорошей отправной точкой для копирования при написании версии этого файла для вашего устройства.
Примечание: Вы можете увидеть сообщение No Command here. Чтобы переключить текст, удерживайте кнопку питания, пока нажимаете кнопку увеличения громкости. Если на вашем устройстве нет обеих кнопок, нажмите и удерживайте любую кнопку, чтобы переключить текст.
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()
вызывается независимо от того, что происходит в оставшейся части восстановления: когда меню выключено, когда оно включено, во время установки пакета, во время очистки пользовательских данных и т. д. Он может возвращать одну из четырех констант:
- TOGGLE . Включить или выключить отображение меню и/или текстового журнала.
- ПЕРЕЗАГРУЗКА . Немедленно перезагрузите устройство.
- ИГНОРИРОВАТЬ . Игнорировать это нажатие клавиши
- ENQUEUE . Поставить это нажатие клавиши в очередь для синхронного использования (т.е. системой меню восстановления, если дисплей включен)
CheckKey()
вызывается каждый раз, когда за событием нажатия клавиши следует событие отпускания клавиши для той же клавиши. (Последовательность событий A-down B-down B-up A-up приводит только к вызову 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()
вызывается в начале восстановления, после инициализации пользовательского интерфейса и анализа аргументов, но до выполнения каких-либо действий. Реализация по умолчанию ничего не делает, поэтому вам не нужно предоставлять это в вашем подклассе, если вам нечего делать:
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 . Установка пакета обновления из разных мест. Подробности см. в разделе Sideloading .
- 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, соберите его и свяжите с recovery на вашем устройстве. В 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. Установка кадра анимации 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
Левый конец заполняющего изображения отображается рядом с правым концом пустого изображения, чтобы создать полосу прогресса. Положение границы между двумя изображениями изменяется, чтобы указать прогресс. Например, с указанными выше парами входных изображений отобразите:

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

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

Рисунок 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"
Каждая функция расширения имеет одинаковую сигнатуру. Аргументами являются имя, по которому была вызвана функция, куки 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, чтобы связать их с 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 включается в файл target-files .zip ( 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
он автоматически извлекает специфичный для устройства модуль из файла 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
.
Механизм боковой загрузки
Recovery имеет механизм боковой загрузки для ручной установки пакета обновления без его загрузки по воздуху основной системой. Боковая загрузка полезна для отладки или внесения изменений на устройствах, где основная система не может быть загружена.
Исторически, сторонняя загрузка выполнялась путем загрузки пакетов с SD-карты устройства; в случае незагружающегося устройства пакет можно поместить на SD-карту с помощью другого компьютера, а затем вставить SD-карту в устройство. Для работы с устройствами Android без съемного внешнего хранилища, восстановление поддерживает два дополнительных механизма для сторонней загрузки: загрузка пакетов из раздела кэша и загрузка их через USB с помощью adb.
Для вызова каждого механизма боковой загрузки метод Device::InvokeMenuItem()
вашего устройства может возвращать следующие значения BuiltinAction:
- Apply_ext . Нагрузка пакета обновлений от внешнего хранилища (
/sdcard
Directory). Ваше восстановление. Fstab должен определить точку крепления/sdcard
. Это не используется на устройствах, которые эмулируют SD -карту с помощью символической ссылки/data
(или некоторого аналогичного механизма)./data
, как правило, недоступны для восстановления, потому что они могут быть зашифрованы. Пользовательский интерфейс восстановления отображает меню файлов .zip в/sdcard
и позволяет пользователю выбрать его. - Apply_cache . Подобно загрузке пакета из
/sdcard
, за исключением того, что каталог/cache
(который всегда доступен для восстановления) используется вместо этого. Из обычной системы/cache
можно записать только привилегированными пользователями, и если устройство не загружается, то каталог/cache
не может быть записан вообще (что делает этот механизм ограниченной утилиты). - Apply_adb_sideload . Позволяет пользователю отправлять пакет на устройство через USB -кабель и инструмент разработки ADB. Когда этот механизм вызывается, восстановление запускает свою собственную мини -версию Daemon ADBD, чтобы позволить ADB на подключенном хост -компьютере поговорить с ним. Эта мини -версия поддерживает только одну команду:
adb sideload filename
. Названный файл отправляется с хост -машины на устройство, которое затем проверяет и устанавливает его так же, как если бы он был на локальном хранилище.
Несколько предостережений:
- Поддерживается только USB -транспорт.
- Если ваше восстановление обычно выполняет ADBD (обычно верно для сборки userdebug и eng), он будет выключен, пока устройство находится в режиме ADB SideLoad и будет перезапущено, когда ADB Sideload завершит получение пакета. Находясь в режиме ADB по боковой нагрузке, нет команд ADB, кроме работы
sideload
(logcat
,reboot
,push
,pull
,shell
и т. Д. - Вы не можете выйти из режима боковой нагрузки ADB на устройстве. Чтобы прервать, вы можете отправлять
/dev/null
(или что -либо еще, что не является действительным пакетом) в качестве пакета, а затем устройство не сможет проверить его и остановить процедуру установки. Метод реализации RecoveryuiCheckKey()
по -прежнему будет вызовом для клавиш, поэтому вы можете предоставить ключевую последовательность, которая перезагружает устройство и работает в режиме ADB Sideload.