設備特定代碼

恢復系統包括幾個用於插入設備特定代碼的掛鉤,以便 OTA 更新也可以更新除 Android 系統之外的設備部分(例如,基帶或無線電處理器)。

以下部分和示例自定義yoyodyne供應商生產的tardis設備。

分區圖

從 Android 2.3 開始,該平台支持 eMMc 閃存設備和在這些設備上運行的 ext4 文件系統。它還支持內存技術設備 (MTD) 閃存設備和舊版本的 yaffs2 文件系統。

分區映射文件由 TARGET_RECOVERY_FSTAB 指定;該文件由恢復二進製文件和包構建工具使用。您可以在 BoardConfig.mk 的 TARGET_RECOVERY_FSTAB 中指定映射文件的名稱。

示例分區映射文件可能如下所示:

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
MTD 閃存設備上的 yaffs2 文件系統。 "device" 必須是 MTD 分區的名稱,並且必須出現在/proc/mtd中。
mtd
原始 MTD 分區,用於可引導分區,例如引導和恢復。 MTD 並沒有實際掛載,而是使用掛載點作為定位分區的鍵。 "device" 必須是/proc/mtd中的 MTD 分區的名稱。
分機4
eMMc 閃存設備上的 ext4 文件系統。 “設備”必須是塊設備的路徑。
emmc
原始 eMMc 塊設備,用於可引導分區,例如引導和恢復。與 mtd 類型類似,eMMc 從未實際掛載,但掛載點字符串用於在表中定位設備。
脂肪
塊設備上的 FAT 文件系統,通常用於外部存儲,例如 SD 卡。該設備是塊設備; device2 是第二個塊設備,如果掛載主設備失敗,系統會嘗試掛載(為了與可能使用分區表格式化或未格式化的 SD 卡兼容)。

所有分區都必須掛載在根目錄中(即掛載點值必須以斜杠開頭,並且沒有其他斜杠)。此限制僅適用於在恢復中掛載文件系統;主系統可以自由安裝在任何地方。目錄/boot/recovery/misc應該是原始類型(mtd 或 emmc),而目錄/system/data/cache/sdcard (如果可用)應該是文件系統類型(yaffs2、ext4 或vfat)。

從 Android 3.0 開始,recovery.fstab 文件獲得了一個額外的可選字段options 。目前唯一定義的選項是length ,它允許您明確指定分區的長度。此長度用於重新格式化分區(例如,在數據擦除/恢復出廠設置操作期間用於用戶數據分區,或在安裝完整 OTA 包期間用於系統分區)。如果長度值為負數,則通過將長度值添加到真實分區大小來獲取要格式化的大小。例如,設置“length=-16384”意味著在重新格式化該分區時不會覆蓋該分區的最後 16k。這支持諸如用戶數據分區加密(其中加密元數據存儲在不應被覆蓋的分區末尾)之類的功能。

注意: device2options字段是可選的,在解析中會產生歧義。如果該行第四個字段中的條目以“/”字符開頭,則將其視為device2條目;如果條目不以“/”字符開頭,則將其視為選項字段。

開機動畫

設備製造商可以自定義 Android 設備啟動時顯示的動畫。為此,請構建一個根據bootanimation 格式的規範組織和定位的 .zip 文件。

對於Android Things設備,您可以在 Android Things 控制台中上傳壓縮文件,以將圖像包含在所選產品中。

注意:這些圖片必須符合 Android品牌指南

恢復界面

為了支持具有不同可用硬件(物理按鈕、LED、屏幕等)的設備,您可以自定義恢復界面以顯示狀態並訪問每個設備的手動操作隱藏功能。

您的目標是構建一個包含幾個 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設備有一個屏幕,因此您可以從內置 ScreenRecoveryUIimplementation 繼承(請參閱沒有屏幕的設備的說明。)從 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 中使用您自己的圖像(錯誤圖標、安裝動畫、進度條)時,您可以設置變量animation_fps來控制動畫的速度(以每秒幀數 (FPS) 為單位)。

注意:當前interlace-frames.py腳本使您能夠將animation_fps信息存儲在圖像本身中。在早期版本的 Android 中,必須自己設置animation_fps

要設置變量animation_fps ,請覆蓋子類中的ScreenRecoveryUI::Init()函數。設置值,然後調用parent Init()函數完成初始化。默認值(20 FPS)對應於默認恢復圖像;使用這些圖像時,您不需要提供Init()函數。有關圖像的詳細信息,請參閱恢復 UI 圖像

設備類

實現 RecoveryUI 後,定義您的設備類(從內置設備類繼承)。它應該創建 UI 類的單個實例並從GetUI()函數返回:

class TardisDevice : public Device {
  private:
    TardisUI* ui;

  public:
    TardisDevice() :
        ui(new TardisUI) {
    }

    RecoveryUI* GetUI() { return ui; }

開始恢復

StartRecovery()方法在恢復開始時調用,在 UI 初始化之後和參數解析之後,但在採取任何操作之前。默認實現什麼都不做,所以如果你無事可做,你不需要在你的子類中提供這個:

   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;
    }

該方法採用鍵代碼(之前已由 UI 對象的CheckKey()方法處理和排隊),以及菜單/文本日誌可見性的當前狀態。返回值是一個整數。如果值為 0 或更高,則將其作為菜單項的位置,立即調用該位置(參見下面的InvokeMenuItem()方法)。否則,它可以是以下預定義常量之一:

  • k突出顯示。將菜單突出顯示移動到上一項
  • kHighlightDown 。將菜單突出顯示移動到下一項
  • kInvokeItem 。調用當前突出顯示的項目
  • 無動作。對此按鍵不執行任何操作

正如可見參數所暗示的那樣,即使菜單不可見,也​​會調用HandleMenuKey() 。與CheckKey()不同,它不會在 recovery 執行諸如擦除數據或安裝包之類的操作時調用——它僅在 recovery 空閒並等待輸入時調用。

軌跡球機制

如果您的設備具有類似軌跡球的輸入機制(生成類型為 EV_REL 和代碼 REL_Y 的輸入事件),只要類似軌跡球的輸入設備報告 Y 軸運動,恢復就會合成 KEY_UP 和 KEY_DOWN 按鍵。您需要做的就是將 KEY_UP 和 KEY_DOWN 事件映射到菜單操作上。 CheckKey()不會發生此映射,因此您不能將軌跡球運動用作重新啟動或切換顯示器的觸發器。

修改鍵

要檢查作為修飾符被按住的鍵,請調用您自己的 UI 對象的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()方法,該方法將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 。從不同的地方安裝更新包。有關詳細信息,請參閱側載
  • 擦除緩存。僅重新格式化緩存分區。無需確認,因為這是相對無害的。
  • 擦除數據。重新格式化用戶數據和緩存分區,也稱為恢復出廠數據。要求用戶在繼續之前確認此操作。

最後一個方法WipeData()是可選的,只要啟動數據擦除操作(通過菜單恢復或用戶選擇從主系統進行出廠數據重置)就會調用。在擦除用戶數據和緩存分區之前調用此方法。如果您的設備將用戶數據存儲在這兩個分區以外的任何位置,則應在此處將其擦除。您應該返回 0 表示成功,返回另一個值表示失敗,儘管目前返回值被忽略。無論您返回成功還是失敗,都會擦除用戶數據和緩存分區。

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

製作設備

最後,在 recovery_ui.cpp 文件的末尾包含一些樣板文件,用於創建並返回 Device 類實例的make_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

恢復 UI 圖像

恢復用戶界面由圖像組成。理想情況下,用戶永遠不會與 UI 交互:在正常更新期間,手機會啟動恢復,填充安裝進度條,並在沒有用戶輸入的情況下啟動回新系統。如果出現系統更新問題,唯一可以採取的用戶操作是致電客戶服務中心。

僅圖像界面消除了本地化的需要。但是,從 Android 5.0 開始,更新可以與圖像一起顯示一串文本(例如“正在安裝系統更新...”)。有關詳細信息,請參閱本地化恢復文本

安卓 5.0 及更高版本

Android 5.0 及更高版本的恢復 UI 使用兩個主要圖像:錯誤圖像和安裝動畫。

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 和更早的恢復 UI 使用錯誤圖像(如上所示)和安裝動畫以及幾個覆蓋圖像:

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.恢復消息的本地化文本

恢復文本可以顯示以下消息:

  • 正在安裝系統更新...
  • 錯誤!
  • 擦除...(進行數據擦除/恢復出廠設置時)
  • 無命令(當用戶手動啟動恢復時)

bootable/recovery/tools/recovery_l10n/中的 Android 應用程序呈現消息的本地化並創建合成圖像。有關使用此應用程序的詳細信息,請參閱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中來提供這些圖像的特定於設備的版本。文件名必須與上面列出的匹配;當在該目錄中找到文件時,構建系統會優先使用它而不是相應的默認圖像。僅支持具有 8 位色深的 RGB 或 RGBA 格式的 PNG。

注意:在 Android 5.x 中,如果區域設置已知可恢復並且是從右到左 (RTL) 的語言(阿拉伯語、希伯來語等),則進度條從右到左填充。

沒有屏幕的設備

並非所有 Android 設備都有屏幕。如果您的設備是無頭設備或只有音頻界面,您可能需要對恢復 UI 進行更廣泛的自定義。與其創建 ScreenRecoveryUI 的子類,不如直接將其父類 RecoveryUI 子類化。

RecoveryUI 具有處理較低級別 UI 操作的方法,例如“切換顯示”、“更新進度條”、“顯示菜單”、“更改菜單選擇”等。您可以覆蓋這些以提供適當的界面為您的設備。也許您的設備有 LED,您可以在其中使用不同的顏色或閃爍模式來指示狀態,或者您可以播放音頻。 (也許您根本不想支持菜單或“文本顯示”模式;您可以使用不會切換顯示或選擇菜單項的CheckKey()HandleMenuKey()實現來阻止訪問它們。在這種情況下,您需要提供的許多 RecoveryUI 方法只能是空存根。)

有關 RecoveryUI 的聲明,請參閱bootable/recovery/ui.h以了解您必須支持哪些方法。 RecoveryUI 是抽象的——一些方法是純虛擬的,必須由子類提供——但它確實包含處理關鍵輸入的代碼。如果您的設備沒有密鑰或者您想以不同的方式處理它們,您也可以覆蓋它。

更新程序

通過提供可以從更新程序腳本中調用的您自己的擴展函數,您可以在更新包的安裝中使用特定於設備的代碼。這是 tardis 設備的示例函數:

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

#include "edify/expr.h"

每個擴展函數都有相同的簽名。參數是調用函數的名稱、 State* cookie、傳入參數的數量以及表示參數的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()

假設該函數需要兩個參數:一個字符串值的和一個 blob 值的圖像。你可以讀到這樣的論點:

   Value* key = EvaluateValue(state, argv[0]);
    if (key == NULL) {
        return NULL;
    }
    if (key->type != VAL_STRING) {
        ErrorAbort(state, "first arg to %s() must be string", name);
        FreeValue(key);
        return NULL;
    }
    Value* image = EvaluateValue(state, argv[1]);
    if (image == NULL) {
        FreeValue(key);    // must always free Value objects
        return NULL;
    }
    if (image->type != VAL_BLOB) {
        ErrorAbort(state, "second arg to %s() must be blob", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

檢查 NULL 並釋放先前評估的參數對於多個參數可能會變得乏味。 ReadValueArgs()函數可以使這更容易。代替上面的代碼,你可以這樣寫:

   Value* key;
    Value* image;
    if (ReadValueArgs(state, argv, 2, &key, &image) != 0) {
        return NULL;     // ReadValueArgs() will have set the error message
    }
    if (key->type != VAL_STRING || image->type != VAL_BLOB) {
        ErrorAbort(state, "arguments to %s() have wrong type", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

ReadValueArgs()不做類型檢查,所以你必須在這裡做;用一個if語句來做這件事會更方便,但代價是它失敗時會產生一個不太具體的錯誤消息。但是,如果任何評估失敗, ReadValueArgs()確實會處理評估每個參數並釋放所有先前評估的參數(以及設置有用的錯誤消息)。您可以使用ReadValueVarArgs()便利函數來評估可變數量的參數(它返回一個Value*數組)。

評估參數後,執行函數的工作:

   // key->data is a NUL-terminated string
    // image->data and image->size define a block of binary data
    //
    // ... some device-specific magic here to
    // reprogram the tardis using those two values ...

返回值必須是Value*對象;該對象的所有權將傳遞給調用者。調用者擁有由此Value*指向的任何數據的所有權——特別是數據成員。

在這種情況下,您希望返回一個 true 或 false 值來表示成功。請記住空字符串為false而所有其他字符串為true的約定。您必須使用 malloc 的常量字符串副本 malloc 一個 Value 對像以返回,因為調用者將free()兩者。不要忘記對通過評估參數獲得的對象調用FreeValue()

   FreeValue(key);
    FreeValue(image);

    Value* result = malloc(sizeof(Value));
    result->type = VAL_STRING;
    result->data = strdup(successful ? "t" : "");
    result->size = strlen(result->data);
    return result;
}

便利函數StringValue()將字符串包裝到新的 Value 對像中。用上面的代碼寫的更簡潔:

   FreeValue(key);
    FreeValue(image);

    return StringValue(strdup(successful ? "t" : ""));
}

要將函數掛鉤到 edify 解釋器,請提供函數Register_ foo ,其中foo是包含此代碼的靜態庫的名稱。調用RegisterFunction()來註冊每個擴展函數。按照慣例,將特定於設備的功能命名為device . whatever避免與未來添加的內置功能發生衝突。

void Register_librecovery_updater_tardis() {
    RegisterFunction("tardis.reprogram", ReprogramTardisFn);
}

您現在可以配置 makefile 以使用您的代碼構建靜態庫。 (這與上一節中用於自定義恢復 UI 的 makefile 相同;您的設備可能在此處定義了兩個靜態庫。)

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

靜態庫的名稱必須與其中包含的Register_ libname函數的名稱相匹配。

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

最後,配置恢復的構建以拉入您的庫。將您的庫添加到 TARGET_RECOVERY_UPDATER_LIBS(可能包含多個庫;它們都已註冊)。如果您的代碼依賴於其他本身不是 edify 擴展的靜態庫(即,它們沒有Register_ libname函數),您可以在 TARGET_RECOVERY_UPDATER_EXTRA_LIBS 中列出這些以將它們鏈接到更新程序,而無需調用它們(不存在的)註冊函數。例如,如果您的設備特定代碼想要使用 zlib 解壓縮數據,您可以在此處包含 libz。

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

# add device-specific extensions to the updater binary
TARGET_RECOVERY_UPDATER_LIBS += librecovery_updater_tardis
TARGET_RECOVERY_UPDATER_EXTRA_LIBS +=

您的 OTA 包中的更新程序腳本現在可以像調用其他任何函數一樣調用您的函數。要重新編程您的 tardis 設備,更新腳本可能包含: tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) 。這使用內置函數package_extract_file()的單參數版本,它將從更新包中提取的文件的內容作為 blob 返回以生成新擴展函數的第二個參數。

OTA包生成

最後一個組件是讓 OTA 包生成工具了解您的設備特定數據並發出更新程序腳本,其中包括對您的擴展函數的調用。

首先,讓構建系統了解特定於設備的數據塊。假設您的數據文件位於device/yoyodyne/tardis/tardis.dat中,請在設備的 AndroidBoard.mk 中聲明以下內容:

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

$(call add-radio-file,tardis.dat)

您也可以將其放在 Android.mk 中,但必須通過設備檢查來保護它,因為無論正在構建什麼設備,都會加載樹中的所有 Android.mk 文件。 (如果您的樹包含多個設備,您只希望在構建 tardis 設備時添加 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

由於歷史原因,這些被稱為無線電文件;它們可能與設備無線電(如果存在)無關。它們只是構建系統複製到 OTA 生成工具使用的目標文件 .zip 中的不透明數據塊。當您進行構建時,tardis.dat 以RADIO/tardis.dat的形式存儲在 target-files.zip 中。您可以多次調用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.dat 文件在兩個構建之間發生更改時才需要重新編程 tardis。

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)輸入目標文件 .zip 的zipfile.ZipFile對象。
  • info.source_zip 。 (僅限增量 OTA)源目標文件 .zip 的zipfile.ZipFile對象(安裝增量包時已在設備上構建)。
  • 信息.target_zip 。 (僅限增量 OTA)目標目標文件 .zip 的zipfile.ZipFile對象(增量包放在設備上的構建)。
  • info.output_zip 。正在創建的包;為寫入而打開的zipfile.ZipFile對象。使用 common.ZipWriteStr(info.output_zip, filename , data ) 將文件添加到包中。
  • 信息腳本。您可以附加命令的腳本對象。調用info.script.AppendExtra( script_text )將文本輸出到腳本中。確保輸出文本以分號結尾,這樣它就不會遇到之後發出的命令。

有關 info 對象的詳細信息,請參閱ZIP 存檔的 Python Software Foundation 文檔

指定模塊位置

在 BoardConfig.mk 文件中指定設備的 releasetools.py 腳本的位置:

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

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

如果未設置 TARGET_RELEASETOOLS_EXTENSIONS,則默認為$(TARGET_DEVICE_DIR)/../common目錄(本例中為device/yoyodyne/common )。最好明確定義 releasetools.py 腳本的位置。在構建 tardis 設備時,releasetools.py 腳本包含在 target-files .zip 文件 ( META/releasetools.py ) 中。

當您運行發布工具( img_from_target_filesota_from_target_files )時,目標文件 .zip 中的 releasetools.py 腳本(如果存在)優先於 Android 源代碼樹中的腳本。您還可以使用-s (或--device_specific )選項顯式指定設備特定擴展的路徑,該選項具有最高優先級。這使您能夠更正錯誤並在 releasetools 擴展中進行更改,並將這些更改應用於舊的目標文件。

現在,當您運行ota_from_target_files時,它會自動從 target_files .zip 文件中獲取特定於設備的模塊,並在生成 OTA 包時使用它:

./build/make/tools/releasetools/ota_from_target_files \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

或者,您可以在運行ota_from_target_files時指定特定於設備的擴展。

./build/make/tools/releasetools/ota_from_target_files \
    -s device/yoyodyne/tardis \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

注意:有關選項的完整列表,請參閱build/make/tools/releasetools/ota_from_target_files中的ota_from_target_files註釋。

側載

恢復具有旁加載機制,用於手動安裝更新包,而無需通過主系統無線下載。旁加載對於在無法啟動主系統的設備上進行調試或更改非常有用。

從歷史上看,側載是通過從設備的 SD 卡上加載包來完成的。在非啟動設備的情況下,可以使用其他計算機將包放入 SD 卡,然後將 SD 卡插入設備。為了適應沒有可移動外部存儲的 Android 設備,恢復支持兩種額外的旁加載機制:從緩存分區加載包,以及使用 adb 通過 USB 加載它們。

要調用每個側載機制,您設備的Device::InvokeMenuItem()方法可以返回以下 BuiltinAction 值:

  • APPLY_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.