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

Система восстановления включает в себя несколько приемов для вставки кода, специфичного для устройства, чтобы 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.

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

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

Ваша цель — создать небольшую статическую библиотеку с парой объектов 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_ДАННЫЕ . Переформатируйте разделы пользовательских данных и кеша, также известный как сброс заводских данных. Прежде чем продолжить, пользователю будет предложено подтвердить это действие.

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

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

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

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

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

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

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

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

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

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

include $(BUILD_STATIC_LIBRARY)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Индикаторы выполнения

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

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

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

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

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

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

индикатор выполнения на 1%

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

индикатор прогресса на 10%

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

индикатор выполнения на 50%

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

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

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

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

Не все устройства 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, чтобы указать на успех. Помните о соглашении, согласно которому пустая строка является ложной , а все остальные строки — истинными . Вы должны выделить объект 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 ). Ваше восстановление. 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 (или что -либо еще, что не является действительным пакетом) в качестве пакета, а затем устройство не сможет проверить его и остановить процедуру установки. Метод реализации Recoveryui CheckKey() по -прежнему будет вызовом для клавиш, поэтому вы можете предоставить ключевую последовательность, которая перезагружает устройство и работает в режиме ADB Sideload.