Код конкретного устройства

Система восстановления включает в себя несколько ловушек для вставки кода, специфичного для устройства, так что обновления 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 .
доб4
Файловая система ext4 на флэш-устройстве eMMc. "устройство" должно быть путем к блочному устройству.
эммк
Необработанное блочное устройство eMMc, используемое для загрузочных разделов, таких как загрузка и восстановление. Подобно типу mtd, eMMc фактически никогда не монтируется, но строка точки монтирования используется для поиска устройства в таблице.
жир
Файловая система FAT поверх блочного устройства, обычно для внешнего хранилища, такого как SD-карта. Устройство является блочным устройством; device2 — это второе блочное устройство, которое система пытается смонтировать в случае сбоя монтирования основного устройства (для совместимости с SD-картами, которые могут быть отформатированы или не отформатированы с помощью таблицы разделов).

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

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

Примечание . Поля device2 и options являются необязательными, что создает неоднозначность при синтаксическом анализе. Если запись в четвертом поле строки начинается с символа '/', она считается записью устройства2 ; если запись не начинается с символа '/', она считается полем параметров .

Загрузочная анимация

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

Для устройств 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, определите класс вашего устройства (подкласс встроенного класса Device). Он должен создать один экземпляр вашего класса пользовательского интерфейса и вернуть его из функции 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() , которая принимает нажатие клавиши и текущую видимость меню и решает, какое действие предпринять:

   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 . Ничего не делать с этим нажатием клавиши

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

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

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

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

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

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

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

InvokeMenuItem

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

include $(BUILD_STATIC_LIBRARY)

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

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

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

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

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

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

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

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

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

Рис. 1. icon_error.png

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Обновление

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

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

#include "edify/expr.h"

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

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

Ваши аргументы не были оценены во время вызова вашей функции — логика вашей функции определяет, какие из них будут оцениваться и сколько раз. Таким образом, вы можете использовать функции расширения для реализации собственных управляющих структур. Call Evaluate() для оценки аргумента Expr* , возвращая Value* . Если Evaluate() возвращает NULL, вы должны освободить все ресурсы, которые у вас есть, и немедленно вернуть NULL (это распространяется вверх по стеку edify). В противном случае вы берете на себя ответственность за возвращаемое значение и несете ответственность за в конечном итоге вызов 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-файл для создания статической библиотеки с вашим кодом. (Это тот же make-файл, который использовался для настройки пользовательского интерфейса восстановления в предыдущем разделе; на вашем устройстве могут быть определены обе статические библиотеки.)

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

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

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Наконец, настройте сборку восстановления для загрузки вашей библиотеки. Добавьте свою библиотеку в TARGET_RECOVERY_UPDATER_LIBS (которая может содержать несколько библиотек; все они регистрируются). Если ваш код зависит от других статических библиотек, которые сами по себе не являются расширениями edify (т. е. у них нет функции Register_ libname ), вы можете перечислить их в TARGET_RECOVERY_UPDATER_EXTRA_LIBS, чтобы связать их с программой обновления без вызова их (несуществующей) функции регистрации. Например, если код вашего устройства хочет использовать 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

По историческим причинам они называются радиофайлами; они могут не иметь ничего общего с радиоустройством (если оно есть). Это просто непрозрачные блоки данных, которые система сборки копирует в целевые файлы .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 может начаться с самого начала. Будьте готовы справиться с устройствами, на которых эти команды уже были запущены полностью или частично.

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

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

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

Для получения подробной информации об информационном объекте обратитесь к документации Python Software Foundation для ZIP-архивов .

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

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

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

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

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

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

Теперь, когда вы запускаете ota_from_target_files , он автоматически берет специфичный для устройства модуль из .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() вашего устройства может возвращать следующие значения BuiltinAction:

  • ПРИМЕНИТЬ_EXT . Загрузите пакет обновления из внешнего хранилища (каталог /sdcard ). В файле recovery.fstab должна быть указана точка монтирования /sdcard . This is not usable on devices that emulate an SD card with a symlink to /data (or some similar mechanism). /data is typically not available to recovery because it may be encrypted. The recovery UI displays a menu of .zip files in /sdcard and allows the user to select one.
  • APPLY_CACHE . Similar to loading a package from /sdcard except that the /cache directory (which is always available to recovery) is used instead. From the regular system, /cache is only writable by privileged users, and if the device isn't bootable then the /cache directory can't be written to at all (which makes this mechanism of limited utility).
  • APPLY_ADB_SIDELOAD . Allows user to send a package to the device via a USB cable and the adb development tool. When this mechanism is invoked, recovery starts up its own mini version of the adbd daemon to let adb on a connected host computer talk to it. This mini version supports only a single command: adb sideload filename . The named file is sent from the host machine to the device, which then verifies and installs it just as if it had been on local storage.

A few caveats:

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