Recovery下显示本地化文字分析

时间:2023-01-15 00:25:56

在本文中我们主要分析google在recovery中根据地区信息(locale)的不同,来显示国际化文字的机制。在recovery下进行升级时,不论是ota升级还是sideload升级方式,如果当前系统区域设置为中文,在进度条上面就会看到“正在安装系统更新…”这几个字,如果改变系统的区域,在这种情况下会看到下面显示的文字已经变为了当前区域下的语言。

在recvoery中要实现以上机制,主要需要完成两个部分,首先是根据区域信息来从图片资源中选取并准确提取出对应区域下的部分图片,然后是绘制提取出的这部分。前者是主要和重点,需要的图片资源提取出来后的绘制相对比较简单。

1 系统的区域信息locale

在recovery.cpp中,有一个全局变量locale,用来保存系统的区域信息。在进入recovery后,会首先读取main system传递给recovery中的参数,将参数中的地区信息保存到变量locale中,如传递给recovery的命令是“–locale=zh_CN”,那么locale便取值为“zh_CN”。如果这一步传给recovery的参数为空,则在recovery中继续调用load_locale_from_cache()

static const char *LOCALE_FILE = "/cache/recovery/last_locale";
static void
load_locale_from_cache() {
FILE* fp = fopen_path(LOCALE_FILE, "r");
char buffer[80];
if (fp != NULL) {
fgets(buffer, sizeof(buffer), fp);
int j = 0;
unsigned int i;
for (i = 0; i < sizeof(buffer) && buffer[i]; ++i) {
if (!isspace(buffer[i])) {
buffer[j++] = buffer[i];
}
}
buffer[j] = 0;
locale = strdup(buffer);
check_and_fclose(fp, LOCALE_FILE);
}
}

在函数load_locale_from_cache()中将直接读取/cache/recovery/last_locale文件中保存的地区信息。

之后在recovery.cpp中调用ui→SetLocale(locale),

void ScreenRecoveryUI::SetLocale(const char* new_locale) {
if (new_locale) {
this->locale = new_locale;
char* lang = strdup(locale);
for (char* p = lang; *p; ++p) {
if (*p == '_') {
*p = '\0';
break;
}
}

// A bit cheesy: keep an explicit list of supported languages
// that are RTL.
if (strcmp(lang, "ar") == 0 || // Arabic
strcmp(lang, "fa") == 0 || // Persian (Farsi)
strcmp(lang, "he") == 0 || // Hebrew (new language code)
strcmp(lang, "iw") == 0 || // Hebrew (old language code)
strcmp(lang, "ur") == 0) { // Urdu
rtl_locale = true;
}
free(lang);
} else {
new_locale = nullptr;
}
}

将locale保存到UI类自己的成员函数locale中,另外要注意的是,这里还会提取出locale信息里下划线‘_’之前的字符,这部分保存的是语言信息,在这里还会判断是否是5种从有向左读取的语言之一。

2 png文件结构分析与应用

之后执行UI类的初始化函数ui→Init(),在其中与显示本地化资源有关的代码是:LoadLocalizedBitmap(“installing_text”, &backgroundText[ INSTALLING_UPDATE]);观察LoadLocalizedBitmap函数的定义,

void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
int result = res_create_localized_alpha_surface(filename, locale, surface);
if (result < 0) {
LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
}
}

在这个函数中直接调用recovery下的minui库中resources.cpp中定义的res_create_localized_alpha_surface函数,这个函数是我们分析的重点,recovery是如何根据传入的locale信息来显示出对应的文字主要就是在这里实现的。

int res_create_localized_alpha_surface(const char* name,
const char* locale,
GRSurface** pSurface) {
GRSurface* surface = NULL;
int result = 0;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_uint_32 width, height;
png_byte channels;
unsigned char* row;
png_uint_32 y;

*pSurface = NULL;

if (locale == NULL) {
surface = malloc_surface(0);
surface->width = 0;
surface->height = 0;
surface->row_bytes = 0;
surface->pixel_bytes = 1;
goto exit;
}

result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
if (result < 0) return result;

if (channels != 1) {
result = -7;
goto exit;
}

row = reinterpret_cast<unsigned char*>(malloc(width));
for (y = 0; y < height; ++y) {
png_read_row(png_ptr, row, NULL);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
int len = row[4];
char* loc = (char*)row+5;

if (y+1+h >= height || matches_locale(loc, locale)) {
printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);

surface = malloc_surface(w*h);
if (surface == NULL) {
result = -8;
goto exit;
}
surface->width = w;
surface->height = h;
surface->row_bytes = w;
surface->pixel_bytes = 1;

int i;
for (i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
memcpy(surface->data + i*w, row, w);
}

*pSurface = reinterpret_cast<GRSurface*>(surface);
break;
} else {
int i;
for (i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
}
}
}

exit:
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
if (result < 0 && surface != NULL) free(surface);
return result;
}

在LoadLocalizedBitmap中调用 res_create_localized_alpha_surface时,传递给这个函数的图片资源便是installing_text.png,在 res_create_localized_alpha_surface中,首先通过open_png函数,读取installing_text.png的相关信息,在open_png中,调用png库来获得png_structp类型的变量png_ptr和 png_infop类型的变量 info_ptr,其中png_ptr变量是在libpng初始化的时候创建,由libpng库内部使用,代表libpng的是调用上下文,根据libpng的api使用说明,库的使用者不应该对这个变量进行访问,在调用libpng的API的时候,需要把这个参数作为第一个参数传入。我们在接下来读取installing_text.png这幅图片中的数据时,主要就是利用这个变量。而 通过info_ptr这个来变量获得png图片的相关属性信息,将installing_text.png这幅图片的宽,高,通道数分别保存到变量width,height,channels中。

接下来根据图片宽度分配一段内存,然后在for循环里调用png_read_row(png_ptr, row, NULL)png_read_row是png库的api,表示一次读取图像中一行像素,因此在for循环中开始时row就保存的是图像的第一行数据。接下来对row进行一系列操作,将结果分别保存到w,h,len,loc这几个变量中,如果不知道google提供的installing_text.png的特殊标记,在这里可能就会感到困惑。

如果放大installing_text.png,仔细观察就会发现图片将所有支持的locale在升级时应该显示的文字按行排列,组成了这幅图片,每一种locale对应的语言在图片中占一部分,同时在每部分的左上角,都会出现几个灰点,recovery中正是利用了这几个灰点像素,将每部分文字需要显示的部分以及对应的locale信息保存在了这里!

Recovery下显示本地化文字分析1.png 

下面我们分析png图片的文件结构,然后将installing_text.png这幅图片中的数据提取出来,我们以res-xxhdpi(1080p)下的installing_text.png为例。

PNG图像格式文件由一个8字节的PNG文件署名(PNG file signature)域和按照特定结构组织的3个以上的数据块(chunk)组成。8字节的PNG文件署名域用来识别该文件是不是PNG文件。该域的值是:16进制 89 50(P) 4e(N) 47(G) 0d 0a 1a 0a,其中50,4e,47分别是大写格式的P,N,G三个字母的16进制ascii码值,下文类似49(I)这种形式都表示这个意思。

首先用16进制文本编辑器winhex打开installing_text.png,文件的前8个字节说明了这是个png文件:

Recovery下显示本地化文字分析2.PNG 

PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。

每个数据块都由4个域组成。
1 Length(长度),4字节,指定数据块中数据域的长度,其长度不超过(2的31次方-1)字节
2 Chunk Type Code(数据块类型码),4字节,数据块类型码由ASCII字母(A-Z和a-z)组成
3 Chunk Data(数据块数据),可变长度,存储按照Chunk Type Code指定的数据
4 CRC(循环冗余检测),4字节,存储用来检测是否有错误的循环冗余码,CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。

关键数据块
关键数据块中的4个标准数据块是:
1 文件头数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
文件头数据块由13字节组成,它的格式为:

Recovery下显示本地化文字分析3.png 

将上述结构与winhex显示出的installing_text.png内容对比,很容易看出

Recovery下显示本地化文字分析4.png 

上面蓝色部分就是整个IHDR数据块,前四个字节00 00 00 0d说明数据块中数据域的长度为13,接下来4字节的49(I) 48(H) 44(D) 52(R)就是IHDR的Chunk Type Code,接下来的13个字节,按照IHDR数据域的格式,可以解析出来的主要信息有:
图像宽度 00 00 03 3e = 830
图像高度 00 00 0a c0 = 2752
图像深度 08
颜色类型 00 ,根据png格式规范,这个值说明图像是8位灰度图像
压缩算法 00 ,根据png格式规范,这个值说明图片采用的是LZ77衍生出的defalte算法,在zip,gzip,pkzip等压缩格式中被支持,因此后面我们可以解压图像像素数据,目前png格式只支持这种压缩方式
最后四字节的 4字节CRC:44 a0 b6 be ,这是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的,与我们通过winhex内置工具计算出来的一致。

Recovery下显示本地化文字分析5.png 

2 调色板数据块PLTE(palette chunk),仅与索引彩色图像有关,因为当前图像是8位灰度图像,不是索引图像,所以不存在PLTE

3 图像数据块IDAT(image data chunk),在这里存储实际的数据。

Recovery下显示本地化文字分析6.png 

IDAT的length为00 02 08 9a = 133274,Chunk Type Code为49(I) 44(D) 41(A) 54(T),因此从54(T)之后开始的 133274个字节的这段数据,就是图像每个像素值被LZ77算法压缩过得数据,54(T)在winhex中是第0×28block,一个block代表一个字节, 0×28 + 133274 , 0×28 + 133274 = 40 +133274 = 0×208c2,因此这段压缩过的数据的范围从0×29 - 0×208c2

Recovery下显示本地化文字分析7.png 

蓝色部分最后的0c位置就是0×208c2
从0×208c3开始的四字节,b0 5c 53 97 就是49(I) 44(D) 41(A) 54(T) 加上之后0×29 - 0×208c2整个这一段的CRC32值。

4 最后一部分,图像结束数据IEND(image trailer chunk),它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。

Recovery下显示本地化文字分析8.png PNG文件结尾12个字符看起来总应该是这样的:00 00 00 00 49 45 4E 44 AE 42 60 82由于数据块结构的定义,IEND数据块的长度总是0, 因此前四个字节为00 00 00 00 ,数据标识总是IEND(49 45 4E 44),因此,CRC也总是AE 42 60 82。

3 像素数据的提取查找以及locale信息的匹配

经过以上分析之后,我们就需要将0×29 - 0×208c2这段数据提取出来,通过pyhthon的zlib库解压,以16进制形式保存到文件中:

#! /usr/bin/env python
import zlib
import binascii
IDAT = "".decode('hex')
decompress = binascii.hexlify(zlib.decompress(IDAT))
print decompress

上面是解压这部分数据的python脚本模板,将提取出来的0×29 - 0×208c2这段数据赋值给IDAT,然后在shell中运行脚本,将输出重定向到一个文件,打开这个文件,可以看到,所有解压出的数据保存在一行中。

Recovery下显示本地化文字分析10.png 

以installing_text的前三行语言为例来分析,它们分别是南非荷兰语(Afrikaans language),阿姆哈拉语(Amharic language),阿拉伯语(Arabic),对应的locale分别是af,am,ar

Recovery下显示本地化文字分析9.png 

因此在刚开始有:
int w = (row[1] « 8) | row[0] = (0×02«8) | 0×25 = 549
int h = (row[3] « 8) | row[2] = (0×00 « 8) | 0×31 = 49
loc 指向的值是0×02616600 ,因为loc为char类型的指针,因此loc 指向的数据按ascii码解析后就是,STX ‘a’ ‘f’ NUL, STX的ascii码为02,表示start of text,NUL就是字符串结束标志。

假设现在recovery中获取到的系统区域locale是am,也就是应该显示第二行文字,这样的话,当我们进入这个for循环后,首先获得的loc 为‘af’,接下来有:

for (y = 0; y < height; ++y) {
png_read_row(png_ptr, row, NULL);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
int len = row[4];
char* loc = (char*)row+5;

if (y+1+h >= height || matches_locale(loc, locale)) {
printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);

surface = malloc_surface(w*h);
if (surface == NULL) {
result = -8;
goto exit;
}
surface->width = w;
surface->height = h;
surface->row_bytes = w;
surface->pixel_bytes = 1;

int i;
for (i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
memcpy(surface->data + i*w, row, w);
}

*pSurface = reinterpret_cast<GRSurface*>(surface);
break;
} else {
int i;
for (i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
}
}
}

目前w=549,h=49,y=0,height = 2752, y+1+h <height,然后在 matches_locale 中,

static int matches_locale(const char* loc, const char* locale) {
if (locale == NULL) return 0;

if (strcmp(loc, locale) == 0) return 1;

// if loc does *not* have an underscore, and it matches the start
// of locale, and the next character in locale *is* an underscore,
// that's a match. For instance, loc == "en" matches locale ==
// "en_US".

int i;
for (i = 0; loc[i] != 0 && loc[i] != '_'; ++i);
if (loc[i] == '_') return 0;

return (strncmp(locale, loc, i) == 0 && locale[i] == '_');
}

在这里程序判断出第一行文字的loc信息‘af’与系统区域信息locale(’am’)不匹配,然后进入到else分支中。
因为现在第一行文字与需要显示的不匹配,因此在这个else分支中,程序还将利用之前计算到的h的值,按行连续读取图像49次,之后继续返回for循环开始,重新调用png_read_row读取像素数据,计算新位置下的w,h,loc值。
因为之前总共已经读取了50行图像,现在重新调用png_read_row将读取的是图像第51行像素值,第51行像素值对应到我们保存的文件中,就是50×830×2+1列开始。

Recovery下显示本地化文字分析11.png 
50×830×2+1列就是83001列,在上图中就是从7d开始这一列,因此,新位置下的w,h,loc值为
int w = (0×01«8)| 0×7d = 381
int h = (row[3] « 8) | row[2] = (0×00 « 8) | 0×31 = 49
因为0×02616d转换为ascii码后就是“am”,所以loc现在指向的值是“am”。下来在 matches_locale中就会判断出找到了匹配当前系统地区信息的loc。图片中的所有语言的匹配都是通过这个方式实现的。

4 图片的分割与显示

最后就会进入到for循环里的if分支,现在已经找到了我们需要显示的文字,下来就开始填充surface指向的GRSurface结构体,GRSurface就是最终可以调用minui库下的api来绘图的变量类型,我们在这里将图片资源提取出来,传给surface:

            surface->width = w;
surface->height = h;
surface->row_bytes = w;
surface->pixel_bytes = 1;

int i;
for (i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
memcpy(surface->data + i*w, row, w);
}

*pSurface = reinterpret_cast<GRSurface*>(surface);
break;

因为在LoadLocalizedBitmap(“mi_installing_text”, &backgroundText[INSTALLING_UPDATE])中传递给res_create_localized_alpha_surface的是backgroundText[INSTALLING_UPDATE],因此这里最后对pSurface的赋值就是对backgroundText[INSTALLING_UPDATE]这个GRSurface的赋值。

上面就是对recvoery下如何根据locale信息开判断并选择出需要显示的文字的整个过程的分析。这个过程中的重点就是对png图片格式的分析,像素数据的解压,并从解压缩后的数据中找到哪些是在installing_text.png中隐藏的文字宽、高以及区域信息。
当成功提取处出在installing_text.png中需要显示的部分后,最后在draw_background_locked中直接调用了minui下的gr_texticon完成了提取出的文字的显示:`GRSurface* text_surface = backgroundText[ icon];gr_texticon(textX, textY, text_surface);`