복구 시스템에는 OTA 업데이트가 Android 시스템이 아닌 기기의 일부(예: 베이스밴드 또는 무선 프로세서)도 업데이트할 수 있도록 기기별 코드를 삽입하기 위한 몇 가지 후크가 있습니다.
다음 섹션과 예에서는 yoyodyne 공급업체에서 생산한 tardis 기기를 맞춤설정합니다.
파티션 맵
Android 2.3부터 플랫폼에서 eMMc 플래시 기기 및 기기에서 실행되는 ext4 파일 시스템을 지원합니다. 또한 MTD(Memory Technology Device) 플래시 기기와 이전 버전의 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
를 제외하고 이 예의 모든 마운트 지점을 정의해야 합니다. 기기는 파티션을 더 추가할 수도 있습니다.
지원되는 파일 시스템 유형은 5가지입니다.
- yaffs2
- MTD 플래시 기기 위의 yaffs2 파일 시스템 'device'는 MTD 파티션의 이름이어야 하며
/proc/mtd
에 표시되어야 합니다. - mtd
- 부팅 및 복구와 같은 부팅 가능한 파티션에 사용되는 원시 MTD 파티션입니다. MTD는 실제로 마운트되지 않지만 마운트 지점은 파티션을 찾는 키로 사용됩니다. 'device'는
/proc/mtd
의 MTD 파티션 이름이어야 합니다. - ext4
- eMMc 플래시 기기 위의 ext4 파일 시스템. 'device'는 블록 기기의 경로여야 합니다.
- emmc
- 부팅 및 복구와 같은 부팅 가능한 파티션에 사용되는 원시 eMMc 블록 기기입니다. mtd 유형과 비슷하게 eMMc는 실제로 마운트되지 않지만 마운트 지점 문자열은 테이블에서 기기를 찾는 데 사용됩니다.
- vfat
- 일반적으로 SD 카드와 같은 외부 저장소의 경우 블록 기기 위의 FAT 파일 시스템. 이 기기는 블록 기기입니다. 파티션 테이블로 포맷할 수 있거나 포맷할 수 없는 SD 카드와의 호환성 때문에 device2는 기본 기기 마운트에 실패할 경우 시스템이 마운트를 시도하는 두 번째 블록 기기입니다.
모든 파티션은 루트 디렉터리에 마운트해야 합니다. 즉 마운트 지점 값은 슬래시로 시작하고 그 외에 다른 슬래시는 없어야 합니다. 이 제한사항은 복구 시 파일 시스템 마운트에만 적용됩니다. 기본 시스템은 어디서든 자유롭게 마운트할 수 있습니다.
/boot
,/recovery
,/misc
디렉터리는 원시 유형(mtd 또는 emmc)이어야 하며/system
,/data
,/cache
,/sdcard
디렉터리(사용 가능한 경우)는 파일 시스템 유형(yaffs2, ext4 또는 vfat)이어야 합니다.
Android 3.0부터 recovery.fstab 파일에는 추가 옵션 필드인 options가 있습니다. 현재 유일하게 정의된 옵션은 파티션의 길이를 명시적으로 지정하도록 하는 length입니다. 이 길이는 파티션을 다시 포맷할 때 사용됩니다(예: 데이터 완전 삭제/초기화 작업 중 사용자 데이터 파티션에 사용, 또는 전체 OTA 패키지를 설치하는 중 시스템 파티션에 사용). 길이 값이 음수이면 실제 파티션 크기에 길이 값을 추가하여 포맷할 크기를 지정합니다. 예를 들어, 'length=-16384'로 설정하면 파티션이 다시 포맷될 때 파티션의 마지막 16k는 덮어쓰지 않습니다. 이는 사용자 데이터 파티션의 암호화와 같은 기능을 지원합니다. 이때 암호화 메타데이터는 파티션 끝에 저장되며 덮어쓰지 않아야 합니다.
참고: device2 및 options 필드는 선택사항이며, 파싱 시 모호함을 일으킵니다. 행의 네 번째 필드에 있는 항목이 '/'문자로 시작하면 device2 항목으로 간주됩니다. 항목이 '/'문자로 시작하지 않으면 options 필드로 간주됩니다.
부팅 애니메이션
기기 제조업체에서는 Android 기기가 부팅될 때 표시되는 애니메이션을 맞춤설정할 수 있습니다. 이를 위해 bootanimation 형식에 지정된 사양에 따라 구성되고 위치가 지정된 .zip 파일을 만듭니다.
Android Things 기기의 경우 선택한 제품에 이미지가 포함되도록 Android Things 콘솔에서 압축 파일을 업로드할 수 있습니다.
참고: 이러한 이미지는 Android 브랜드 가이드라인을 준수해야 합니다.
복구 UI
사용 가능한 다른 하드웨어(물리적 버튼, 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"
헤더 및 항목 함수
기기 클래스에는 숨겨진 복구 메뉴에 표시되는 헤더 및 항목을 반환하는 함수가 필요합니다. 헤더는 메뉴를 조작하는 방법을 설명합니다. 즉, 강조표시된 항목을 변경/선택하는 컨트롤입니다.
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 상수
KEY_* 상수는 linux/input.h
에 정의되어 있습니다.
CheckKey()
는 메뉴가 사용 중지되어 있을 때, 사용 설정되어 있을 때, 패키지 설치 중, 사용자 데이터 완전 삭제 중 등 복구의 나머지 부분에서 진행되는 작업에 상관없이 호출되고 다음 네 가지 상수 중 하나를 반환할 수 있습니다.
- TOGGLE. 메뉴 또는 텍스트 로그 표시를 사용 설정 또는 사용 중지로 전환합니다.
- REBOOT. 기기를 즉시 재부팅합니다.
- IGNORE. 키 누름을 무시합니다.
- ENQUEUE. 예를 들어, 디스플레이가 사용 설정된 경우 복구 메뉴 시스템에서 동기적으로 소비하도록 키 누름을 대기열에 추가합니다.
CheckKey()
는 동일한 키에 키 다운 이벤트가 발생한 다음 키 업 이벤트가 발생할 때마다 호출됩니다. A-down B-down B-up A-up 이벤트 순서는 CheckKey(B)
만 호출합니다. CheckKey()
는 IsKeyPressed()
를 호출하여 다른 키가 눌러져 있는지 확인할 수 있습니다. 위의 키 이벤트 순서에서 CheckKey(B)
가 IsKeyPressed(A)
를 호출했다면 true를 반환했을 것입니다.
CheckKey()
는 자체 클래스에서 상태를 유지할 수 있습니다. 이는 키 순서를 감지하는 데 유용할 수 있습니다. 이 예에서는 약간 더 복잡한 설정을 보여줍니다. 전원 버튼을 누른 상태에서 볼륨 업 버튼을 눌러 디스플레이를 전환하고 전원 버튼을 5회 연속으로 눌러 기기를 재부팅할 수 있습니다(그 외에 개입하는 키 없음).
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()
함수를 호출하여 초기화를 완료합니다.
기본값(20FPS)은 기본 복구 이미지에 상응합니다. 이러한 이미지를 사용할 때는 Init()
함수를 제공하지 않아도 됩니다.
이미지에 관한 자세한 내용은 복구 UI 이미지를 참고하세요.
기기 클래스
RecoveryUI를 구현한 후 내장 기기 클래스에서 서브클래스로 분류된 기기 클래스를 정의합니다. UI 클래스의 단일 인스턴스를 만들어 GetUI()
함수에서 반환해야 합니다.
class TardisDevice : public Device { private: TardisUI* ui; public: TardisDevice() : ui(new TardisUI) { } RecoveryUI* GetUI() { return ui; }
StartRecovery
StartRecovery()
메서드는 복구가 시작될 때, UI가 초기화된 후, 인수가 파싱된 후, 작업이 실행되기 전 호출됩니다. 기본 구현은 아무 작업도 실행하지 않으므로 실행할 작업이 없는 경우에는 서브클래스에서 제공할 필요가 없습니다.
void StartRecovery() { // ... do something tardis-specific here, if needed .... }
복구 메뉴 제공 및 관리
시스템은 두 개의 메서드를 호출하여 헤더 행 목록 및 항목 목록을 가져옵니다. 이 구현에서는 파일의 맨 위에 정의된 정적 배열을 반환합니다.
const char* const* GetMenuHeaders() { return HEADERS; } const char* const* GetMenuItems() { return ITEMS; }
HandleMenuKey
그런 다음 키 누름과 현재 메뉴 가시성을 사용하여 실행할 작업을 판단하는 HandleMenuKey()
함수를 제공합니다.
int HandleMenuKey(int key, int visible) { if (visible) { switch (key) { case KEY_VOLUMEDOWN: return kHighlightDown; case KEY_VOLUMEUP: return kHighlightUp; case KEY_POWER: return kInvokeItem; } } return kNoAction; }
이 메서드는 키 코드(UI 객체의 CheckKey()
메서드에서 이전에 처리되어 대기열에 추가됨)와 메뉴/텍스트 로그 가시성의 현재 상태를 사용합니다. 반환 값은 정수입니다.
값이 0 이상이면 바로 호출되는 메뉴 항목의 위치로 간주됩니다. 아래 InvokeMenuItem()
메서드를 참고하세요. 그 외에는 다음 사전 정의된 상수 중 하나일 수 있습니다.
- kHighlightUp. 메뉴 강조표시를 이전 항목으로 이동
- kHighlightDown. 메뉴 강조표시를 다음 항목으로 이동
- kInvokeItem. 현재 강조표시된 항목 호출
- kNoAction. 이 키를 누를 때 아무 작업도 하지 않음
표시 인수에서 암시된 것처럼 HandleMenuKey()
는 메뉴가 표시되지 않더라도 호출됩니다. CheckKey()
와 달리 데이터 완전 삭제 또는 패키지 설치와 같은 복구 작업을 실행하는 동안에는 호출되지 않으며 복구가 유휴 상태에서 입력을 대기할 때만 호출됩니다.
트랙볼 메커니즘
기기에 트랙볼과 유사한 입력 메커니즘이 있는 경우(유형이 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()
에서 반환한 항목의 배열에서 정수 위치를 작업에 매핑하는 InvokeMenuItem()
메서드를 제공합니다. 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 enum의 모든 멤버를 반환하여 시스템에 작업을 실행하도록 알립니다(또는 시스템에서 아무 작업도 실행하지 않도록 하려면 NO_ACTION 멤버를 반환합니다). 시스템에 있는 항목 외에 추가 복구 기능을 제공하는 위치입니다. 메뉴에 항목을 추가하고 메뉴 항목을 호출할 때 여기에서 실행하고 NO_ACTION을 반환하여 시스템이 다른 작업을 실행하지 않도록 합니다.
BuiltinAction에는 다음 값이 포함됩니다.
- NO_ACTION. 아무 작업도 실행하지 않습니다.
- REBOOT. 복구를 종료하고 기기를 정상적으로 재부팅합니다.
- 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()
함수의 상용구를 포함합니다.
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부터 업데이트 시 이미지와 함께 텍스트 문자열을 표시할 수 있습니다(예: '시스템 업데이트 설치 중...'). 자세한 내용은 현지화된 복구 텍스트를 참고하세요.
Android 5.0 이상
Android 5.0 이상 복구 UI는 오류 이미지와 설치 애니메이션, 이렇게 두 가지 기본 이미지를 사용합니다.
설치 애니메이션은 애니메이션의 여러 프레임이 행으로 인터레이스된 단일 PNG 이미지로 표시됩니다. 따라서 그림 2가 찌그러져 보입니다. 예를 들어 200x200 7프레임 애니메이션의 경우 첫 번째 프레임이 행 0, 7, 14, 21, ... 인 200x1400 이미지 하나를 만듭니다. 두 번째 프레임은 행 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는 위에 표시된 오류 이미지와 설치 애니메이션 및 여러 오버레이 이미지를 사용합니다.
설치하는 동안 화면 디스플레이는 icon_installing.png 이미지를 그린 다음 상단의 오버레이 프레임 중 하나를 적절한 오프셋으로 그려 구성됩니다. 여기에서 기본 이미지 위에 오버레이가 배치되는 위치를 강조하기 위해 빨간색 상자가 표시됩니다.
후속 프레임은 이미 위에 있는 다음 오버레이 이미지만 그리면 표시됩니다. 기본 이미지는 다시 그려지지 않습니다.
애니메이션의 프레임 수, 원하는 속도, 베이스를 기준으로 한 오버레이의 x 및 y 오프셋은 ScreenRecoveryUI 클래스의 멤버 변수로 설정됩니다. 기본 이미지 대신 맞춤 이미지를 사용하는 경우 서브클래스의 Init()
메서드를 재정의하여 이러한 맞춤 이미지의 값을 변경합니다. 자세한 내용은 ScreenRecoveryUI를 참고하세요. bootable/recovery/make-overlay.py
스크립트는 필수 오프셋 계산을 비롯하여 복구에 필요한 '기본 이미지 + 오버레이 이미지' 형식으로 이미지 프레임 세트를 변환하는 데 도움이 될 수 있습니다.
기본 이미지는 bootable/recovery/res/images
에 있습니다. 설치 중에 정적 이미지를 사용하려면 icon_installing.png 이미지를 제공하고 애니메이션의 프레임 수를 0으로 설정하기만 하면 됩니다. 오류 아이콘은 애니메이션이 아니며 항상 정적 이미지입니다.
현지화된 복구 텍스트
Android 5.x는 이미지와 함께 텍스트 문자열(예: '시스템 업데이트 설치 중...')을 표시합니다. 기본 시스템이 복구로 부팅되면 사용자의 현재 언어를 복구에 명령줄 옵션으로 전달합니다. 표시할 각 메시지의 경우 복구에는 메시지의 각 언어로 사전 렌더링된 텍스트 문자열과 함께 두 번째 합성 이미지가 포함됩니다.
복구 텍스트 문자열의 샘플 이미지:
복구 텍스트는 다음 메시지를 표시할 수 있습니다.
- 시스템 업데이트 설치 중...
- 오류
- 삭제 중...(데이터 완전 삭제 또는 초기화 수행 중)
- 명령어가 없음(사용자가 직접 복구로 부팅하는 경우)
bootable/recovery/tools/recovery_l10n/
의 Android 앱은 메시지 현지화를 렌더링하고 합성 이미지를 만듭니다. 이 앱 사용에 관한 자세한 내용은 bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java
의 주석을 참고하세요.
사용자가 복구로 직접 부팅하면 언어를 사용할 수 없으며 텍스트가 표시되지 않을 수 있습니다. 문자 메시지를 복구 프로세스에 중요하게 만들지 마세요.
참고: 로그 메시지를 표시하고 사용자가 메뉴에서 작업을 선택하도록 허용하는 숨겨진 인터페이스는 영어로만 제공됩니다.
진행률 표시줄
진행를 표시줄은 기본 이미지(또는 애니메이션) 아래에 표시될 수 있습니다. 진행률 표시줄은 두 개의 입력 이미지를 결합하여 만들어지며 크기는 동일해야 합니다.
채우기 이미지의 왼쪽 끝이 빈 이미지의 오른쪽 끝 옆에 표시되어 진행률 표시줄을 만듭니다. 두 이미지 사이의 경계 위치가 바뀌면서 진행 상황을 나타냅니다. 예를 들어 위 입력 이미지 쌍의 경우 다음을 표시합니다.
이 예에서는 이러한 이미지를 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*
쿠키, 들어오는 인수의 수, 인수를 나타내는 Expr*
포인터의 배열입니다. 반환 값은 새로 할당된 Value*
입니다.
Value* ReprogramTardisFn(const char* name, State* state, int argc, Expr* argv[]) { if (argc != 2) { return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc); }
인수는 함수가 호출될 때 평가되지 않습니다. 함수의 논리가 평가되는 인수와 평가 횟수를 결정합니다. 따라서 확장 함수를 사용하여 자체 제어 구조를 구현할 수 있습니다. Call Evaluate()
는 Expr*
인수를 평가하고 Value*
를 반환합니다. Evaluate()
가 NULL을 반환하면 보유하고 있는 리소스를 모두 해제하고 NULL을 즉시 반환해야 합니다. 이렇게 하면 edify 스택 위로 중단이 전파됩니다. 그렇지 않으면 반환된 Value의 소유권을 가져와 결국 FreeValue()
호출을 책임집니다.
함수에 문자열 값 키와 blob 값 이미지라는 두 개의 인수가 필요하다고 가정해 보겠습니다. 인수를 다음과 같이 읽을 수 있습니다.
Value* key = EvaluateValue(state, argv[0]); if (key == NULL) { return NULL; } if (key->type != VAL_STRING) { ErrorAbort(state, "first arg to %s() must be string", name); FreeValue(key); return NULL; } Value* image = EvaluateValue(state, argv[1]); if (image == NULL) { FreeValue(key); // must always free Value objects return NULL; } if (image->type != VAL_BLOB) { ErrorAbort(state, "second arg to %s() must be blob", name); FreeValue(key); FreeValue(image) return NULL; }
NULL을 확인하고 이전에 평가된 인수를 해제하면 인수가 많아 지루할 수 있습니다. ReadValueArgs()
함수를 사용하면 이 작업을 더 쉽게 실행할 수 있습니다. 위의 코드 대신 다음과 같이 작성할 수 있습니다.
Value* key; Value* image; if (ReadValueArgs(state, argv, 2, &key, &image) != 0) { return NULL; // ReadValueArgs() will have set the error message } if (key->type != VAL_STRING || image->type != VAL_BLOB) { ErrorAbort(state, "arguments to %s() have wrong type", name); FreeValue(key); FreeValue(image) return NULL; }
ReadValueArgs()
는 유형 확인을 실행하지 않으므로 여기에서 실행해야 합니다. 실패할 경우 좀 덜 구체적인 오류 메시지를 생성하더라도 if 문 하나로 작업을 실행하는 것이 훨씬 편리합니다.
그러나 ReadValueArgs()
는 평가 실패 시 각 인수를 평가하고 이전에 평가된 모든 인수를 해제하고 유용한 오류 메시지를 설정합니다. 가변적인 수의 인수를 평가하는 데
ReadValueVarArgs()
편의 함수를 사용할 수 있습니다(Value*
배열을 반환함).
인수를 계산한 후 함수의 작업을 실행합니다.
// key->data is a NUL-terminated string // image->data and image->size define a block of binary data // // ... some device-specific magic here to // reprogram the tardis using those two values ...
반환 값은 Value*
객체여야 합니다. 이 객체의 소유권은 호출자에게 전달됩니다. 호출자는 이 Value*
가 가리키는 모든 데이터(특히 datamember)의 소유권을 가져옵니다.
이 경우에는 true 또는 false 값을 반환하여 성공을 나타내고자 합니다. 비어 있는 문자열은 false이고 다른 모든 문자열은 true라는 규칙을 기억하세요. 호출자가 둘 다를 free()
하므로 반환할 상수 문자열의 malloc'd 사본과 함께 Value 객체를 malloc해야 합니다. 인수를 평가하여 가져온 객체에서 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 패키지 생성 도구를 가져와 기기 관련 데이터를 확인하고 확장 함수 호출을 포함하는 업데이터 스크립트를 내보냅니다.
먼저 빌드 시스템에 데이터의 기기별 blob에 관해 알립니다.
데이터 파일이 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 생성 도구에서 사용하는 target-files .zip으로 복사하는 데이터의 불투명 blob에 불과합니다. 빌드를 실행하면 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.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 설치가 처음부터 다시 시작될 수 있습니다. 이러한 명령어가 전체 또는 부분적으로 이미 실행된 기기에 대처할 준비가 되어 있어야 합니다.
함수를 정보 객체에 전달
여러 유용한 항목이 포함된 단일 정보 객체에 함수를 전달합니다.
- info.input_zip. (전체 OTA에만 적용) 입력 target-files .zip의
zipfile.ZipFile
객체 - info.source_zip. (증분 OTA에만 적용) 소스 target-files .zip의
zipfile.ZipFile
객체(증분 패키지가 설치될 때 이미 기기에 설치된 빌드) - info.target_zip. (증분 OTA에만 적용) 타겟 target-files .zip의
zipfile.ZipFile
객체(증분 패키지가 기기에 설치하는 빌드) - info.output_zip. 패키지 생성 중 작성하기 위해 열린
zipfile.ZipFile
객체. common.ZipWriteStr(info.output_zip, filename, data)을 사용하여 파일을 패키지에 추가합니다. - info.script. 명령어를 추가할 수 있는 스크립트 객체.
info.script.AppendExtra(script_text)
를 호출하여 텍스트를 스크립트에 출력합니다. 이후에 내보낸 명령어로 실행되지 않도록 출력 텍스트가 세미콜론으로 끝나는지 확인하세요.
정보 객체에 관한 자세한 내용은 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_files
또는 ota_from_target_files
)를 실행할 때 target-files .zip의 releasetools.py 스크립트(있는 경우)가 Android 소스 트리의 스크립트보다 우선합니다. 최우선순위를 갖는 -s
(또는 --device_specific
) 옵션을 사용하여 기기별 확장 프로그램의 경로를 명시적으로 지정할 수도 있습니다. 이를 통해 오류를 수정하고, releasetools 확장 프로그램을 변경하고, 이전 target-files에 변경사항을 적용할 수 있습니다.
이제 ota_from_target_files
를 실행하면 target_files .zip 파일에서 자동으로 기기별 모듈을 선택하고 OTA 패키지를 생성할 때 사용합니다.
./build/make/tools/releasetools/ota_from_target_files \
-i PREVIOUS-tardis-target_files.zip \
dist_output/tardis-target_files.zip \
incremental_ota_update.zip
또는 ota_from_target_files
를 실행할 때 기기별 확장 프로그램을 지정할 수 있습니다.
./build/make/tools/releasetools/ota_from_target_files \
-s device/yoyodyne/tardis \
-i PREVIOUS-tardis-target_files.zip \
dist_output/tardis-target_files.zip \
incremental_ota_update.zip
참고: 전체 옵션 목록은
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
마운트 지점을 정의해야 합니다./data
심볼릭 링크(또는 유사한 메커니즘)를 사용해 SD 카드를 에뮬레이션하는 기기에서는 유용하지 않습니다./data
는 암호화될 수 있으므로 일반적으로 복구에 사용할 수 없습니다. 복구 UI에서는/sdcard
에 .zip 파일 메뉴를 표시하고 사용자가 선택하도록 허용합니다. - APPLY_CACHE.
/sdcard
에서 패키지를 로드하는 것과 비슷합니다. 단, 항상 복구에 사용할 수 있는/cache
디렉터리를 대신 사용한다는 점이 다릅니다. 일반 시스템에서/cache
는 권한이 있는 사용자만 쓰기 가능하며, 기기를 부팅할 수 없으면/cache
디렉터리에 아예 쓸 수 없으므로 이 메커니즘의 유용성은 제한적입니다. - APPLY_ADB_SIDELOAD. 사용자가 USB 케이블 및 adb 개발 도구를 통해 기기로 패키지를 보낼 수 있도록 허용합니다. 이 메커니즘이 호출되면 복구는 adbd 데몬의 자체 미니 버전을 시작하여 연결된 호스트 컴퓨터의 adb가 이 미니 버전과 통신하도록 합니다. 이 미니 버전은 단일 명령어
adb sideload filename
만 지원합니다. 이름이 지정된 파일은 호스트 머신에서 기기로 전송된 다음 기기가 마치 로컬 저장소에 있는 것처럼 확인 및 설치됩니다.
몇 가지 주의 사항:
- USB 전송만 지원됩니다.
- 복구가 adbd를 정상적으로 실행하면(userdebug 및 eng 빌드의 경우 일반적으로 true), 기기가 adb 사이드로드 모드에 있는 동안 종료되며 adb 사이드로드가 패키지 수신을 완료하면 다시 시작됩니다. adb 사이드로드 모드에 있는 동안
sideload
이외의 adb 명령어는 작동하지 않습니다.logcat
,reboot
,push
,pull
,shell
등 모두 실패합니다. - 기기에서 adb 사이드로드 모드를 종료할 수 없습니다. 중지하려면
/dev/null
(또는 유효한 패키지가 아닌 그외 모든 항목)을 패키지로 전송하면 됩니다. 그러면 기기가 확인에 실패하고 설치 절차를 중지합니다. RecoveryUI 구현의CheckKey()
메서드는 키를 누를 때 계속 호출되므로 기기를 재부팅하고 adb 사이드로드 모드에서 작동하는 키 시퀀스를 제공할 수 있습니다.