地表最強之Android開機Logo動態替換(附100餘款車商原廠高清logo)

前言

現在很多車載大屏都是基於Android系統,其中都會有一個開機 logo 替換程序,任你選擇各大車商 logo 替換,一開機就能查看效果。

一直對這塊很感興趣,研究了發現 MTK 平臺可以通過 Nvram 來存儲數據,這是一種可行的方案,應用層寫入index,uboot 和

kernel 讀取 index 繪製對應的 logo 以達到動態替換效果。

100餘款車商logo資源

YO8AFf.png

開機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生成過程

Y5Y6DU.png

注:圖片來自

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、實現思路

Yf0Lg1.png

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 下載鏈接

YfBi8A.png

簡單驗證後能正常讀寫 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;
}

/*
 * Show kernel logo when phone boot up
 *
 */
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 圖片

最終效果

修改前
YO4jbj.png

修改後
YO4ujg.png<

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章