Система восстановления включает в себя несколько приемов для вставки кода, специфичного для устройства, чтобы 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. «устройство» должно быть именем раздела MTD и должно присутствовать в
/proc/mtd
. - мтд
- Необработанный раздел MTD, используемый для загрузочных разделов, таких как загрузочный и восстановительный. MTD на самом деле не монтируется, но точка монтирования используется как ключ для поиска раздела. «устройство» должно быть именем раздела MTD в
/proc/mtd
. - ext4
- Файловая система ext4 на флэш-устройстве eMMc. «устройство» должно быть путем к блочному устройству.
- эммк
- Необработанное блочное устройство eMMc, используемое для загрузочных разделов, таких как загрузка и восстановление. Подобно типу mtd, eMMc фактически никогда не монтируется, но строка точки монтирования используется для поиска устройства в таблице.
- жирный
- Файловая система FAT на блочном устройстве, обычно для внешнего хранилища, например SD-карты. Устройство является блочным устройством; устройство2 — это второе блочное устройство, которое система пытается смонтировать в случае сбоя монтирования основного устройства (для совместимости с SD-картами, которые могут быть или не быть отформатированы с использованием таблицы разделов).
Все разделы должны быть смонтированы в корневом каталоге (т.е. значение точки монтирования должно начинаться с косой черты и не иметь других косых черт). Это ограничение применяется только к монтированию файловых систем при восстановлении; основная система может свободно монтировать их где угодно. Каталоги
/boot
,/recovery
и/misc
должны иметь необработанные типы (mtd или emmc), а каталоги/system
,/data
,/cache
и/sdcard
(если доступны) должны быть типами файловой системы (yaffs2, ext4 или вфат).
Начиная с Android 3.0, файл Recovery.fstab получает дополнительное необязательное поле options . В настоящее время единственной определенной опцией является length , которая позволяет явно указать длину раздела. Эта длина используется при переформатировании раздела (например, для раздела пользовательских данных во время операции очистки данных/восстановления заводских настроек или для системного раздела во время установки полного пакета OTA). Если значение длины отрицательное, то форматируемый размер определяется путем прибавления значения длины к истинному размеру раздела. Например, установка «длина=-16384» означает, что последние 16 КБ этого раздела не будут перезаписаны при переформатировании этого раздела. Это поддерживает такие функции, как шифрование раздела пользовательских данных (где метаданные шифрования хранятся в конце раздела, которые не следует перезаписывать).
Примечание. Поля «устройство2» и «Параметры» являются необязательными, что создает неоднозначность при синтаксическом анализе. Если запись в четвертом поле строки начинается с символа «/», она считается записью устройства2 ; если запись не начинается с символа «/», она считается полем параметров .
Загрузочная анимация
Производители устройств имеют возможность настраивать анимацию, отображаемую при загрузке устройства Android. Для этого создайте ZIP-файл, организованный и расположенный в соответствии со спецификациями в формате бутанимации .
На устройствах 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()
вызывается независимо от того, что происходит в остальной части восстановления: когда меню выключено, когда оно включено, во время установки пакета, во время очистки пользовательских данных и т. д. Он может возвращать одну из четырех констант:
- ПЕРЕКЛЮЧАТЬ . Включить или выключить отображение меню и/или текстового журнала.
- ПЕРЕЗАГРУЗИТЬ . Немедленно перезагрузите устройство
- ИГНОРИРОВАТЬ . Игнорировать это нажатие клавиши
- ОЧЕРЕДЬ . Поставьте это нажатие клавиши в очередь для синхронного использования (т. е. системой меню восстановления, если дисплей включен)
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 кадров в секунду) соответствует образам восстановления по умолчанию; при использовании этих изображений вам не нужно предоставлять функцию Init()
. Подробную информацию об изображениях см . в разделе «Образы пользовательского интерфейса восстановления» .
Класс устройства
После того, как у вас есть реализация RecoveryUI, определите класс вашего устройства (подкласс встроенного класса устройства). Он должен создать единственный экземпляр вашего класса пользовательского интерфейса и вернуть его из функции GetUI()
:
class TardisDevice : public Device { private: TardisUI* ui; public: TardisDevice() : ui(new TardisUI) { } RecoveryUI* GetUI() { return ui; }
Запустить Recovery
Метод StartRecovery()
вызывается в начале восстановления, после инициализации пользовательского интерфейса и анализа аргументов, но до выполнения каких-либо действий. Реализация по умолчанию ничего не делает, поэтому вам не нужно указывать это в своем подклассе, если вам нечего делать:
void StartRecovery() { // ... do something tardis-specific here, if needed .... }
Поставка и управление меню восстановления
Система вызывает два метода, чтобы получить список строк заголовков и список элементов. В этой реализации он возвращает статические массивы, определенные в верхней части файла:
const char* const* GetMenuHeaders() { return HEADERS; } const char* const* GetMenuItems() { return ITEMS; }
РучкаМенюКлюч
Затем предоставьте функцию 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 . Переместить выделение меню на предыдущий пункт
- kВыделить вниз . Переместить выделение меню на следующий пункт
- 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 } ... }
Примечание. Если видимость имеет значение 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; } }
Этот метод может возвращать любой член перечисления BuildinginAction, чтобы сообщить системе о необходимости выполнения этого действия (или член NO_ACTION, если вы хотите, чтобы система ничего не делала). Здесь можно предоставить дополнительные функции восстановления помимо того, что есть в системе: добавьте для него элемент в свое меню, выполните его здесь, когда этот элемент меню вызывается, и верните NO_ACTION, чтобы система больше ничего не делала.
ВстроенныйДействие содержит следующие значения:
- НЕТ_АКЦИЯ . Ничего не делайте.
- ПЕРЕЗАГРУЗИТЬ . Выйдите из рекавери и перезагрузите устройство в обычном режиме.
- APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD . Установите пакет обновлений из разных мест. Подробности см. в разделе Загрузка неопубликованных файлов .
- WIPE_CACHE . Переформатируйте только раздел кэша. Подтверждения не требуется, поскольку это относительно безвредно.
- WIPE_ДАННЫЕ . Переформатируйте разделы пользовательских данных и кеша, также известный как сброс заводских данных. Прежде чем продолжить, пользователю будет предложено подтвердить это действие.
Последний метод, 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 и более поздних версий используются два основных изображения: изображение ошибки и анимация установки .
Анимация установки представлена в виде одного 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 и более ранних версий использует изображение ошибки (показанное выше), анимацию установки и несколько наложенных изображений:
Во время установки экранное меню создается путем рисования изображения icon_installing.png, а затем рисования одного из наложенных фреймов поверх него с правильным смещением. Здесь наложен красный прямоугольник, чтобы выделить место наложения поверх базового изображения:
Последующие кадры отображаются путем рисования только следующего наложенного изображения поверх уже существующего; базовое изображение не перерисовывается.
Количество кадров в анимации, желаемая скорость, а также смещения по оси X и Y наложения относительно основы задаются переменными-членами класса ScreenRecoveryUI. При использовании пользовательских изображений вместо изображений по умолчанию переопределите метод Init()
в своем подклассе, чтобы изменить эти значения для ваших пользовательских изображений (подробнее см. в ScreenRecoveryUI ). Скрипт bootable/recovery/make-overlay.py
может помочь преобразовать набор кадров изображения в форму «базовое изображение + наложенные изображения», необходимую для восстановления, включая вычисление необходимых смещений.
Образы по умолчанию расположены в bootable/recovery/res/images
. Чтобы использовать статическое изображение во время установки, вам нужно только предоставить изображение icon_installing.png и установить количество кадров в анимации равным 0 (значок ошибки не анимирован, это всегда статическое изображение).
Локализованный текст восстановления
Android 5.x отображает текстовую строку (например, «Установка обновления системы...») вместе с изображением. Когда основная система загружается в режим восстановления, она передает текущую локаль пользователя в качестве параметра командной строки для восстановления. Для каждого отображаемого сообщения восстановление включает второе составное изображение с предварительно обработанными текстовыми строками для этого сообщения в каждой локали.
Пример изображения текстовых строк для восстановления:
Текст восстановления может отображать следующие сообщения:
- Установка обновления системы...
- Ошибка!
- Стирание... (при выполнении очистки данных/сброса настроек)
- Нет команды (когда пользователь загружается в рекавери вручную)
Приложение Android в bootable/recovery/tools/recovery_l10n/
визуализирует локализацию сообщения и создает составное изображение. Подробную информацию об использовании этого приложения см. в комментариях в bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
.
Когда пользователь загружается в режим восстановления вручную, локаль может быть недоступна и текст не отображается. Не делайте текстовые сообщения критически важными для процесса восстановления.
Примечание. Скрытый интерфейс, отображающий сообщения журнала и позволяющий пользователю выбирать действия из меню, доступен только на английском языке.
Индикаторы выполнения
Индикаторы выполнения могут отображаться под основным изображением (или анимацией). Индикатор выполнения создается путем объединения двух входных изображений, которые должны быть одинакового размера:
Левый конец заполненного изображения отображается рядом с правым концом пустого изображения, образуя индикатор выполнения. Положение границы между двумя изображениями изменяется, чтобы указать на прогресс. Например, с помощью приведенных выше пар входных изображений отобразите:
Вы можете предоставить версии этих образов для конкретных устройств, поместив их (в этом примере) в 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"
Каждая функция расширения имеет одну и ту же сигнатуру. Аргументами являются имя, по которому была вызвана функция, файл 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). В противном случае вы берете на себя ответственность за возвращаемое значение и несете ответственность за в конечном итоге вызов 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, чтобы указать на успех. Помните о соглашении, согласно которому пустая строка является ложной , а все остальные строки — истинными . Вы должны выполнить malloc для объекта Value с помощью malloc-копии константной строки для возврата, поскольку вызывающая сторона будет использовать оба метода 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-файл для создания статической библиотеки с вашим кодом. (Это тот же файл 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()
с одним аргументом, которая возвращает содержимое файла, извлеченного из пакета обновления, в виде большого двоичного объекта для создания второго аргумента новой функции расширения.
Генерация 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 добавлялся только файл tardis.dat.)
device/yoyodyne/tardis/Android.mk
[...] # an alternative to specifying it in AndroidBoard.mk ifeq (($TARGET_DEVICE),tardis) $(call add-radio-file,tardis.dat) endif
По историческим причинам их называют радиофайлами; они могут не иметь никакого отношения к радиоустройству устройства (если оно имеется). Это просто непрозрачные блоки данных, которые система сборки копирует в целевые файлы .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
для целевых файлов .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-файл целевых файлов ( META/releasetools.py
).
Когда вы запускаете инструменты выпуска ( img_from_target_files
или ota_from_target_files
), сценарий Releasetools.py в ZIP-файле target-files, если он присутствует, предпочтительнее сценария из дерева исходного кода Android. Вы также можете явно указать путь к расширениям, специфичным для устройства, с помощью параметра -s
(или --device_specific
), который имеет высший приоритет. Это позволяет исправлять ошибки и вносить изменения в расширения Releasetools, а также применять эти изменения к старым целевым файлам.
Теперь, когда вы запускаете 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
.
Механизм боковой загрузки
Recovery имеет механизм неопубликованной загрузки , позволяющий вручную устанавливать пакет обновлений без его беспроводной загрузки основной системой. Неопубликованная загрузка полезна для отладки или внесения изменений на устройствах, где основная система не может быть загружена.
Исторически неопубликованная загрузка осуществлялась путем загрузки пакетов с SD-карты устройства; В случае, если устройство не загружается, пакет можно поместить на SD-карту с помощью другого компьютера, а затем вставить SD-карту в устройство. Для поддержки устройств Android без съемного внешнего накопителя восстановление поддерживает два дополнительных механизма загрузки неопубликованных приложений: загрузка пакетов из раздела кэша и загрузка их через USB с помощью adb.
Чтобы вызвать каждый механизм неопубликованной загрузки, метод Device::InvokeMenuItem()
вашего устройства может возвращать следующие значения встроенного действия:
- ПРИМЕНИТЬ_EXT . Загрузите пакет обновления из внешнего хранилища (каталог
/sdcard
). Ваш файл Recovery.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.