前言
现在很多车载大屏都是基于Android系统,其中都会有一个开机 logo 替换程序,任你选择各大车商 logo 替换,一开机就能查看效果。
一直对这块很感兴趣,研究了发现 MTK 平台可以通过 Nvram 来存储数据,这是一种可行的方案,应用层写入index,uboot 和
kernel 读取 index 绘制对应的 logo 以达到动态替换效果。
100余款车商logo资源
开机Logo加载原理
1、u-boot logo显示原理
Little Kernel 会在 platform_early_init 阶段首先会获取 lcm params,其工作流程就是透过读id找到现在插入的LCM,
根据 LCM 的分辨率申请相应大小的 frame buffer并确定 frame buffer 起始地址,接着为 logo.bin 预留4M Ram
之后在 platform_init 阶段,直接将 logo.bin 载入到 4M Ram 中
完成载入后,在 platform_init 中 mt_disp_show_boot_logo() 会调用show_logo(0);完成第一张logo显示。
其中的index=0代表在 logo.bin 中压缩的第一张图片,logo.bin 中的图片压缩顺序可以察看文件
vendor\mediatek\proprietary\bootable\bootloader\lk\dev\logo\rules.mk
2、kernel logo 显示原理
Kernel logo 的工作方式与 U-boot logo 不同,是透过init.rc中注册的,boot_logo_updater service 完成读取raw data文件,
进行绘画的,所以在 kernel logo 只是经过了 bmp 向 raw 的转换,在目录 vendor\mediatek\proprietary\bootable\bootloader\lk\dev\logo\ 下
通过 bmp_to_raw 工具将 bmp 转换为 raw 文件,之后透过脚本文件将 boot_logo 文件搬移到
out\target\product\xxx\obj\BOOTLOADER_OBJ\build-xxx\dev\logo\wxga下,打包到 logo.bin,download 到手机中
3、logo.bin生成过程
注:图片来自
https://blog.csdn.net/u011784994/article/details/105561958
4、替换原理
U-boot logo,将不同分辨率的图片压缩至 logo.bin 中,在读取时根据不同的 NVflag 显示相应的图片即可
platform.c 中 lk_display_show_logo(void) 读取 NVflag 传递给 mt_logo.c 调用 void mt_disp_show_boot_logo(int logo_index)
Kernel logo,需要将不同分辨率的 boot_logo raw data文件生成出来并 copy 到手机中,boot_logo_updater根据不同的分辨率进行识别,
读取相应的logo文件。在 boot_logo_updater 识别部分对 NVflag 进行判断,进而调用不同的boot_logo。
charging_animation.cpp 中 void show_kernel_logo() 读取 NVflag
开机Logo制作
根据 device/mediateksample/xxx/ProjectConfig.mk 中 BOOT_LOGO= wxga 对应字段,找到
vendor\mediatek\proprietary\bootable\bootloader\lk\dev\logo\wxga 文件夹中对应的图片分辨率
在 PS 中新建同等分辨率,置入素材,最终保存为 bmp 格式即可。
知识储备
1、实现思路
2、NVram 相关介绍
NVRAM( Non-Volatile Random Access Memory) 是非易失性随机访问存储器,指断电后仍能保持数据的一种 RAM。
MTK 源码中提供了相关的工具类读写,写入 NVRam 中的数据恢复出厂也不会丢失。
有关 NVRAM 可查看 MTK NVRAM
3、不同区域读取 NVRam 数据
应用层读写 Nvram 方法网上有很多,而且很容易验证。uboot 和 kernel 读取比较稀少,找了很久参考如下的文章
不同区域读取NVRAM数据
经过N次不停修改尝试后,最终苍天不负,终于搞定。
有了以上对于 NVram 相关认知,那我们就来撸代码吧。
动态替换实现
1、java 层读写 NVram
java 层主要通过 INvram 接口来读写,对应的需要导入静态库,
LOCAL_STATIC_JAVA_LIBRARIES += vendor.mediatek.hardware.nvram-V1.0-java-static
显然在AS 中是编译不过的,所以只能放到源码环境通过配置 Android.mk 的方式编译。
测试用 Android.mk 文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#引入静态库读写nvram
LOCAL_STATIC_JAVA_LIBRARIES += vendor.mediatek.hardware.nvram-V1.0-java-static
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := NvramDemo
#编译后的apk路径在data目录下
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
include $(BUILD_PACKAGE)
也有可能你的源码中 nvram 版本不是 V1.0,可以通过 find -name “*.java” | xargs grep “/vendor/nvdata/” 命令搜索
那个 app 也操作了 nvram,复制对应mk中的版本号就行。
测试用 NVActivity 代码如下
package com.android.qrdc;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Arrays;
import vendor.mediatek.hardware.nvram.V1_0.INvram;
import com.android.internal.util.HexDump;
public class NVActivity extends Activity {
private static final String TAG = "NVActivity";
private TextView tv_result;
private EditText cmdEt;
private static void log(String msg) {
Log.e("NVActivity", msg);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.nv_layout);
tv_result = findViewById(R.id.tv_result);
cmdEt = findViewById(R.id.cmdEt);
}
public void readNv(View v) {
log("readNv click");
tv_result.setText("read result"+ readData());
}
public void writeNv(View v) {
String cmd = cmdEt.getText().toString();
log("writeNv click -----" + cmd);
writeData(Integer.parseInt(cmd));
}
@Override
protected void onDestroy() {
super.onDestroy();
}
public static final String PRODUCT_INFO_FILENAME = "/vendor/nvdata/APCFG/APRDEB/PRODUCT_INFO";
private static void writeData(int n) {
byte[] write_buff = new byte[]{0, 0, 0, 0};
byte[] by = getBytes(n);
for (int i = 0; i < 4; i++) {
write_buff[i] = by[i];
}
try {
INvram agent = INvram.getService();
if (agent != null) {
ArrayList<Byte> dataArray = new ArrayList<>(4);
for (byte b : write_buff) {
dataArray.add(new Byte(b));
}
int ret_1 = agent.writeFileByNamevec(PRODUCT_INFO_FILENAME, 537, dataArray);
if (ret_1>0){
log("write success"+ ret_1);
}else {
log("write failed"+ ret_1);
}
} else {
Log.e(TAG, "writeData: agent null");
}
} catch (Exception e) {
Log.e(TAG, "writeData exception:" + e.getLocalizedMessage());
e.printStackTrace();
}
}
private static byte[] getBytes(int data) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (data & 0xff);
bytes[1] = (byte) ((data & 0xff00) >> 8);
bytes[2] = (byte) ((data & 0xff0000) >> 16);
bytes[3] = (byte) ((data & 0xff000000) >> 24);
return bytes;
}
public static int readData() {
int targets = 0;
try {
String buff = null;
INvram agent = INvram.getService();
Log.i(TAG, "readData from PRODUCT_INFO_FILENAME");
if (agent != null) {
buff = agent.readFileByName(PRODUCT_INFO_FILENAME, 537);
}
byte[] buffArr = HexDump.hexStringToByteArray(buff.substring(0, buff.length() - 1));
targets = (buffArr[0] & 0xff) | ((buffArr[1] << 8) & 0xff00) | ((buffArr[2] << 24) >>> 8) | (buffArr[3] << 24);
Log.i(TAG, "readData: buffArr=" + Arrays.toString(buffArr) + ", targets == " + targets);
} catch (Exception e) {
Log.e(TAG, "readData exception:" + e.getLocalizedMessage());
e.printStackTrace();
}
return targets;
}
}
测试 demo 运行图,demo apk 下载链接
简单验证后能正常读写 nvram,那我们就需要对 demo 进行大改造,思路如下
1、预置开机logo缩略图,通过 GridView 加载展示
2、点击 GridView 中图片弹框提示是否需要替换开机logo
3、确定替换将logo缩略图对应index值写入 nvram 中
4、重启可查看效果
核心读写 nvram 方法已提供,其它代码太多就不贴了。
修改代码清单
vendor/mediatek/proprietary/bootable/bootloader/lk/dev/logo/rules.mk
vendor/mediatek/proprietary/bootable/bootloader/lk/dev/logo/update
vendor/mediatek/proprietary/bootable/bootloader/lk/platform/mt6735/include/platform/mt_logo.h
vendor/mediatek/proprietary/bootable/bootloader/lk/platform/mt6735/mt_logo.c
vendor/mediatek/proprietary/bootable/bootloader/lk/platform/mt6735/platform.c
vendor/mediatek/proprietary/external/libshowlogo/Android.mk
vendor/mediatek/proprietary/external/libshowlogo/charging_animation.cpp
vendor/mediatek/proprietary/external/libshowlogo/charging_animation.h
2、native 层读取 NVram
kernel logo 绘制方法位于 charging_animation.cpp 中 void show_kernel_logo()
头文件中增加 int nvram_read_index(void) 获取 index 方法
vendor\mediatek\proprietary\external\libshowlogo\charging_animation.h
+++ b/alps/vendor/mediatek/proprietary/external/libshowlogo/charging_animation.h
@@ -110,7 +110,7 @@ void anim_surface_deinit(void);
// show logo function
void anim_show_logo(int index);
-
+int nvram_read_index(void);
#ifdef __cplusplus
}
vendor\mediatek\proprietary\external\libshowlogo\charging_animation.cpp
//cczheng add read kernel logo index from nvram
#define MAX_RETRY_COUNT 100
int nvram_read_index(void){
int logo_index = kernel_logo_position;
int read_nvram_ready_retry = 0;
F_ID fid;
int rec_size = 0;
int rec_num = 0;
int nvinfo_lid = AP_CFG_REEB_PRODUCT_INFO_LID;
char *nvdata_result;
bool isread = false;
char nvram_init_val[128] = {0};
SLOGE("Entry get_barcode_from_nvram");
while (read_nvram_ready_retry < MAX_RETRY_COUNT) {
read_nvram_ready_retry++;
property_get("service.nvram_init", nvram_init_val, NULL);
if (strcmp(nvram_init_val, "Ready") == 0) {
break;
} else {
usleep(500 * 1000);
}
}
if (read_nvram_ready_retry >= MAX_RETRY_COUNT) {
SLOGE("Get nvram restore ready failed!");
return logo_index;
}
nvdata_result = (char *) malloc(1024);
if (nvdata_result == NULL) {
return logo_index;
}
fid = NVM_GetFileDesc(nvinfo_lid, &rec_size, &rec_num, isread);
if (fid.iFileDesc < 0) {
SLOGE("fid.iFileDesc < 0");
free(nvdata_result);
return logo_index;
}
if (rec_size != read(fid.iFileDesc, nvdata_result, rec_size)) {
free(nvdata_result);
return logo_index;
}
free(nvdata_result);
if (!NVM_CloseFileDesc(fid)) {
return logo_index;
}
SLOGE("nvramstr hex buff[0]%x, buff[1]%x, buff[2]%x, buff[3]%x \n",
nvdata_result[0],nvdata_result[1],nvdata_result[2],nvdata_result[3]);
SLOGE("The size of nvdata_result:%d\n", sizeof(nvdata_result));
SLOGE("nvramstr dex buff[0]=%d, buff[1]=%d, buff[2]=%d, buff[3]=%d \n",
nvdata_result[0],nvdata_result[1],nvdata_result[2],nvdata_result[3]);
logo_index = nvdata_result[0];
return logo_index;
}
void show_kernel_logo()
{
if (MTK_LOG_ENABLE == 1) {
SLOGD("[libshowlogo: %s %d]show kernel logo, index = 38 \n",__FUNCTION__,__LINE__);
}
if (error_flag == 0) {
#if defined(MTK_CARRIEREXPRESS_PACK)
anim_show_logo(get_logo_index(false));
SLOGE("[show_kernel_logo: get_logo_index %d", get_logo_index(false));
#else
//cczheng add read index from nvram data
kernel_logo_position = nvram_read_index();
anim_show_logo(kernel_logo_position);
SLOGE("[show_kernel_logo: %d %d]show kernel logo, \n",__LINE__, kernel_logo_position);
#endif
}
}
同时还需要在 Android.mk 中导入所需 nvram 库
vendor\mediatek\proprietary\external\libshowlogo\Android.mk
+++ b/alps/vendor/mediatek/proprietary/external/libshowlogo/Android.mk
@@ -50,8 +50,8 @@ endif
endif
endif
-LOCAL_SHARED_LIBRARIES := libcutils libutils libc libstdc++ libz libdl liblog libgui libui libsysenv_system libbase
-
+LOCAL_SHARED_LIBRARIES := libcutils libutils libc libstdc++ libz libdl liblog libgui libui libsysenv_system libbase libnvram_mtk libcustom_nvram_mtk
LOCAL_STATIC_LIBRARIES += libfs_mgr
LOCAL_C_INCLUDES += $(MTK_PATH_CUSTOM)/lk/include/target
@@ -62,6 +62,14 @@ LOCAL_C_INCLUDES += $(TOP)/frameworks/native/libs/nativewindow/include
LOCAL_C_INCLUDES += system/core/fs_mgr/include
LOCAL_C_INCLUDES += $(TOP)/vendor/mediatek/proprietary/external/libsysenv
+LOCAL_C_INCLUDES += vendor/mediatek/proprietary/external/nvram/libnvram \
+ vendor/mediatek/proprietary/custom/ \
+ $(MTK_PATH_SOURCE)/external/nvram/libfile_op \
+ $(MTK_PATH_CUSTOM)/cgen/inc \
+ $(MTK_PATH_CUSTOM)/cgen/cfgfileinc \
+ vendor/mediatek/proprietary/custom/common/cgen/cfgfileinc \
+ vendor/mediatek/proprietary/custom/common/cgen/inc
+
LOCAL_MODULE := libshowlogo
#LOCAL_PROPRIETARY_MODULE := true
#LOCAL_MODULE_OWNER := mtk
调试小技巧
通过 jni log 打印读取的数据,查找其中index值,其中需要进制转换,分别打印了 16 进制和 10 进制,通过 log 查找正确的结果
烧写时只勾选 lk、boot、logo 替换即可,加快烧写验证速度。
3、uboot 区域读取 NVram
uboot 阶段相比 kernel 要困难太多,一开始没法看日志,不知道读取的值是否正确,后来灵机一动,加上去除之前 oem 解锁后屏幕
显示警告文字也是在 uboot 阶段显示,那这里应该也行,果不其然通过 video_printf() 打印日志调试就很方便,mtk 真是太牛逼了!
uboot logo 绘制方法位于 mt_logo.cpp 中 void mt_disp_show_boot_logo(void)
修改头文件,传递 index 值,显示动态替换logo
vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt6735\include\platform\mt_logo.h
+++ b/alps/vendor/mediatek/proprietary/bootable/bootloader/lk/platform/mt6735/include/platform/mt_logo.h
@@ -58,7 +58,8 @@ const LOGO_CUST_IF *LOGO_GetCustomIF(void);
void mt_disp_enter_charging_state(void);
void mt_disp_show_battery_full(void);
void mt_disp_show_battery_capacity(UINT32 capacity);
-void mt_disp_show_boot_logo(void);
+// void mt_disp_show_boot_logo(void);
+void mt_disp_show_boot_logo(int logo_index);
void mt_disp_show_low_battery(void);
修改实现方法,参数传递 logo_index
vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt6735\mt_logo.c
+++ b/alps/vendor/mediatek/proprietary/bootable/bootloader/lk/platform/mt6735/mt_logo.c
@@ -266,9 +266,12 @@ void mt_logo_get_custom_if(void)
* Show first boot logo when phone boot up
*
*/
-void mt_disp_show_boot_logo(void)
-{
- int logo_index = 0;
+//cczheng change param void to int
+// void mt_disp_show_boot_logo(void)
+void mt_disp_show_boot_logo(int logo_index)
+{
+ //cczheng annotaion
+ //int logo_index = 0;
dprintf(INFO, "[lk logo: %s %d]\n",__FUNCTION__,__LINE__);
mt_logo_get_custom_if();
if (logo_cust_if->show_boot_logo) {
logo_cust_if->show_boot_logo();
} else {
///show_logo(0);
init_fb_screen();
fill_animation_logo(logo_index, mt_get_fb_addr(), mt_get_tempfb_addr(), logo_addr, phical_screen);
mt_disp_update(0, 0, CFG_DISPLAY_WIDTH, CFG_DISPLAY_HEIGHT);
}
return;
}
platform 中读取 nvram 值传递给 mt_logo
vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt6735\platform.c
//cczheng add uboot read nvram logo index
int get_bootlogo_flag(void) {
long len, length;
u64 start_address;
part_t *part;
part_dev_t *dev;
char *part_name = "proinfo";
dev = mt_part_get_device();
if (!dev) {
video_printf(" => !dev is true...\n");
return 0;
}
part = mt_part_get_partition(part_name);
if (!part) {
video_printf(" => mboot_get_boot_logo_flag--- part = NULL\n");
dprintf(INFO, "mboot_get_boot_logo_flag--- part = NULL");
return 0;
}
dprintf(INFO, "mboot_get_boot_logo_flag--- get_partition 1\n");
length = 1024;
start_address = (u64)part->start_sect * dev->blkdev->blksz;
video_printf(" => start_address=%llu\n", start_address);//524288
unsigned char *addr;
addr = (unsigned char *) malloc(length * sizeof(unsigned char));
int index = partition_get_index(part_name);
if (index == -1) {
video_printf(" => partition_get_index == -1\n");
dprintf(INFO, "mboot_get_boot_logo_flag--- partition_get_index =%d\n", index);
return 0;
}
unsigned int part_id = partition_get_region(index);
len = dev->read(dev, start_address, (uchar *) addr, length, part_id);
if (len < 0) {
video_printf(" => dev->read len < 0\n");
dprintf(INFO, "mboot_get_boot_logo_flag--- dev->read len=%ld\n", len);
return 0;
}
video_printf(" => part_dev->read addr=%s\n", addr);//&
video_printf("The size of nvdata_result:%d\n", sizeof(addr));//4
video_printf("nvramstr dex buff[0]=%d, buff[1]=%d, buff[2]=%d, buff[3]=%d \n",
addr[0],addr[1],addr[2],addr[3]);//26
int result = addr[0];//boot logo flag
dprintf(INFO,"mboot_get_boot_logo_flag addr = %d\n",result);
video_printf(" => result=%d\n", result);
return result;
}
static void lk_display_show_logo(void)
{
#ifdef LK_PROFILING
unsigned int time_show_logo = get_timer(0);
#endif // LK_PROFILING
#ifndef MACH_FPGA_NO_DISPLAY
// mt_disp_show_boot_logo();
//cczheng add read logo index and pass to mt_logo
int logo_index = get_bootlogo_flag();
video_printf(" => mt_disp_show_boot_logo==%d\n", logo_index);
mt_disp_show_boot_logo(logo_index);
#endif // MACH_FPGA_NO_DISPLAY
logo_lk_t = ((unsigned int)get_timer(boot_time));
#ifdef LK_PROFILING
dprintf(CRITICAL, "[PROFILE] show logo takes %d ms\n", (int)get_timer(time_show_logo));
#endif // LK_PROFILING
}
4、预置logo资源打包到rom中
经过上面修改,读写 nvram 都已正常,最后预置 logo bmp 图片打包即可,还需修改如下两个地方
在 (BOOT_LOGO)_kernel.raw 后面增加要打包进的资源图片名称,可配置 N 个,index值在 kernel 后依次累加。
vendor\mediatek\proprietary\bootable\bootloader\lk\dev\logo\rules.mk
ifeq ($(strip $(SUPPORT_CARRIEREXPRESS_PACK)),yes)
RESOURCE_OBJ_LIST += \
$(BOOT_LOGO_DIR)/$(BASE_LOGO)/$(BASE_LOGO)_kernel.raw
else
RESOURCE_OBJ_LIST += \
$(BOOT_LOGO_DIR)/$(BOOT_LOGO)/$(BOOT_LOGO)_kernel.raw
endif
endif
endif
+RESOURCE_OBJ_LIST += \
+ $(BOOT_LOGO_DIR)/$(BOOT_LOGO)/$(BOOT_LOGO)_uboot_kernel.raw
增加对应的 bmp 转化 raw 规则
vendor\mediatek\proprietary\bootable\bootloader\lk\dev\logo\update
+++ b/alps/vendor/mediatek/proprietary/bootable/bootloader/lk/dev/logo/update
@@ -44,6 +44,7 @@ p=$1
./tool/bmp_to_raw ./temp36.raw ./$p/"${p}_bat_img".bmp
./tool/bmp_to_raw ./temp37.raw ./$p/"${p}_bat_100".bmp
./tool/bmp_to_raw ./boot_logo ./$p/"${p}_kernel".bmp
+./tool/bmp_to_raw ./boot_logo ./$p/"${p}_uboot_kernel".bmp
./tool/zpipe -l 9 ./"${p}.raw" temp0.raw temp1.raw temp2.raw temp3.raw temp4.raw temp5.raw temp6.raw temp7.raw temp8.raw temp9.raw temp10.raw temp11.raw temp12.raw temp13.raw temp14.raw temp15.raw temp16.raw temp17.raw temp18.raw temp19.raw temp20.raw temp21.raw temp22.raw temp23.raw temp24.raw temp25.raw temp26.raw temp27.raw temp28.raw temp29.raw temp30.raw temp31.raw temp32.raw temp33.raw temp34.raw temp35.raw temp36.raw temp37.raw
rm -rf ./temp0.raw ./temp1.raw ./temp2.raw ./temp3.raw ./temp4.raw ./temp5.raw ./temp6.raw ./temp7.raw ./temp8.raw ./temp9.raw ./temp10.raw ./temp11.raw ./temp12.raw ./temp13.raw ./temp14.raw ./temp15.raw ./temp16.raw ./temp17.raw ./temp18.raw ./temp19.raw ./temp20.raw ./temp21.raw ./temp22.raw ./temp23.raw ./temp24.raw ./temp25.raw ./temp26.raw ./temp27.raw ./temp28.raw ./temp29.raw ./temp30.raw ./temp31.raw ./temp32.raw ./temp33.raw ./temp34.raw ./temp35.raw ./temp36.raw ./temp37.raw ./bootlogo.raw
echo "conversion finished"
在对应文件下放置 wxga_uboot_kernel.bmp 图片
最终效果
修改前
修改后
<