Android7關機充電流程


2021-09-03:Android7關機充電流程

背景

為了修改關機充電中的顯示效果,因此學習一下Android 7關機充電的流程是怎么樣的。

以msm8909為例。

[   94.741021] charger: [94654] animation starting
[   94.744542] charger: animation starting cur_frame =0

參考:

動畫的初始化

main

// system/core/healthd/healthd.cpp

struct healthd_mode_ops *healthd_mode_ops;

static struct healthd_mode_ops charger_ops = {
    .init = healthd_mode_charger_init,
    .preparetowait = healthd_mode_charger_preparetowait,
    .heartbeat = healthd_mode_charger_heartbeat,
    .battery_update = healthd_mode_charger_battery_update,
};

// main函數根據參數賦值不同ops給healthd_mode_ops
int main(int argc, char **argv) {
    int ch;
    int ret;

    klog_set_level(KLOG_LEVEL);
    healthd_mode_ops = &android_ops;

    if (!strcmp(basename(argv[0]), "charger")) {
        healthd_mode_ops = &charger_ops;
    } else {
        while ((ch = getopt(argc, argv, "cr")) != -1) {
            switch (ch) {
            case 'c':
                healthd_mode_ops = &charger_ops;
                break;
            case 'r':
                healthd_mode_ops = &recovery_ops;
                break;
            }
        }
    }
	// 初始化、顯示以及循環
    ret = healthd_init();

    periodic_chores();
    healthd_mode_ops->heartbeat();

    healthd_mainloop();
    KLOG_ERROR("Main loop terminated, exiting\n");
    return 3;
}

healthd_init

// system/core/healthd/healthd.cpp
static struct healthd_config healthd_config = {
    .periodic_chores_interval_fast = DEFAULT_PERIODIC_CHORES_INTERVAL_FAST,
    .periodic_chores_interval_slow = DEFAULT_PERIODIC_CHORES_INTERVAL_SLOW,
    .batteryStatusPath = String8(String8::kEmptyString),
    .batteryHealthPath = String8(String8::kEmptyString),
    .batteryPresentPath = String8(String8::kEmptyString),
    .batteryCapacityPath = String8(String8::kEmptyString),
    .batteryVoltagePath = String8(String8::kEmptyString),
    .batteryTemperaturePath = String8(String8::kEmptyString),
    .batteryTechnologyPath = String8(String8::kEmptyString),
    .batteryCurrentNowPath = String8(String8::kEmptyString),
    .batteryCurrentAvgPath = String8(String8::kEmptyString),
    .batteryChargeCounterPath = String8(String8::kEmptyString),
    .batteryFullChargePath = String8(String8::kEmptyString),
    .batteryCycleCountPath = String8(String8::kEmptyString),
    .energyCounter = NULL,
    .boot_min_cap = 0,
    .screen_on = NULL,
};

static int healthd_init() {
    // 創建epoll
    epollfd = epoll_create(MAX_EPOLL_EVENTS);
    if (epollfd == -1) {
        KLOG_ERROR(LOG_TAG,
                   "epoll_create failed; errno=%d\n",
                   errno);
        return -1;
    }

    healthd_board_init(&healthd_config);
    // 注冊監聽的句柄
    healthd_mode_ops->init(&healthd_config);
    wakealarm_init();
    // 添加其他句柄(socket)
    uevent_init();
    gBatteryMonitor = new BatteryMonitor();
    gBatteryMonitor->init(&healthd_config);
    return 0;
}

healthd_mode_charger_init

// system/core/healthd/healthd_mode_charger.cpp
void healthd_mode_charger_init(struct healthd_config* config)
{
    int ret;
    // // charger的狀態是charger_state
    struct charger *charger = &charger_state;
    int i;
    int epollfd;

    // 初始化kernel log
    dump_last_kmsg();

    LOGW("--------------- STARTING CHARGER MODE ---------------\n");

    healthd_board_mode_charger_init();
	// 注冊事件處理函數,包括監聽輸入,電池喚醒
    ret = ev_init(input_callback, charger);
    if (!ret) {
        epollfd = ev_get_epollfd();
        healthd_register_event(epollfd, charger_event_handler, EVENT_WAKEUP_FD);
    }

    // 解析animation.txt文件的內容,初始化animation結構體,下面會詳細分析
    // (大家可以先閱讀這個函數的內容是什么,然后再返回這里往下看)
    struct animation* anim = init_animation();
    charger->batt_anim = anim;

	// 看函數名及參數可知,用於創建電池狀態出錯的界面
    ret = res_create_display_surface(anim->fail_file.c_str(), &charger->surf_unknown);
    if (ret < 0) {
        LOGE("Cannot load custom battery_fail image. Reverting to built in.\n");
        ret = res_create_display_surface("charger/battery_fail", &charger->surf_unknown);
        if (ret < 0) {
            LOGE("Cannot load built in battery_fail image\n");
            charger->surf_unknown = NULL;
        }
    }
#ifdef CHARGER_USER_ANIMATION
        GRSurface* scale_frames[USER_IMAGE_NUM];

        for(int i = 0; i<USER_IMAGE_NUM; i++){
            ret = res_create_display_surface(anim->user_animation_file[i].c_str(), &scale_frames[i]);
                if (ret < 0) {
                    LOGE("Cannot load custom %s image. Reverting to built in.\n",anim->user_animation_file[i].c_str());
                }else{
                    anim->frames[i].surface = scale_frames[i];
                    LOGW("file is:[%s],anim->frames[%d].surface = charger->surf_unknown;\n",
                        anim->user_animation_file[i].c_str(),i);
                }
        }
#else
    GRSurface** scale_frames;
    int scale_count;
    int scale_fps;  // Not in use (charger/battery_scale doesn't have FPS text
                    // chunk). We are using hard-coded frame.disp_time instead.
    // 從函數名可知,從png文件中創建多幀動畫界面
    // 這個函數會根據png圖片頭部信息,將一張png圖切成多個frame
    // 因為之前UI部門給的圖片都是動畫切出來的多張圖片,所以這個函數很少用到
    ret = res_create_multi_display_surface(anim->animation_file.c_str(),
        &scale_count, &scale_fps, &scale_frames);
    if (ret < 0) {
        LOGE("Cannot load battery_scale image\n");
        anim->num_frames = 0;
        anim->num_cycles = 1;
    } else if (scale_count != anim->num_frames) {
        LOGE("battery_scale image has unexpected frame count (%d, expected %d)\n",
             scale_count, anim->num_frames);
        anim->num_frames = 0;
        anim->num_cycles = 1;
    } else {
        for (i = 0; i < anim->num_frames; i++) {
            anim->frames[i].surface = scale_frames[i];
        }
    }
#endif
    ev_sync_key_state(set_key_callback, charger);

    charger->next_screen_transition = -1;
    charger->next_key_check = -1;
    charger->next_pwr_check = -1;
    healthd_config = config;
    charger->boot_min_cap = config->boot_min_cap;
}

創建epoll的fd,創建關機鬧鍾的處理線程(創建一個新的epoll,並讓關機鬧鍾處理事件epoll_wait在上面,如果關機鬧鍾事件觸發,那么直接以rtc的開機原因熱重啟)

init_animation

// system/core/healthd/healthd_mode_charger.cpp
animation* init_animation()
{
    bool parse_success;

    std::string content;
    // 讀取animation.txt文件信息,animation_desc_path定義如下
    // static constexpr const char* animation_desc_path="/res/values/charger/animation.txt";
    if (base::ReadFileToString(animation_desc_path, &content)) {
        // 解析讀取到的animation文件的內容,該函數在下面分析
        // 大家也可以往下拉看怎么分析的,再回來這里往下看
        parse_success = parse_animation_desc(content, &battery_animation);
    } else {
        LOGW("Could not open animation description at %s\n", animation_desc_path);
        parse_success = false;
    }

    if (!parse_success) {
        // 解析失敗,使用默認的animation動畫
        LOGW("Could not parse animation description. Using default animation.\n");
        battery_animation = BASE_ANIMATION;
#ifdef CHARGER_USER_ANIMATION
        battery_animation.user_animation_file[0].assign("charger/battery00");
        battery_animation.user_animation_file[1].assign("charger/battery01");
        //battery_animation.user_animation_file[2].assign("charger/battery02");
        //battery_animation.user_animation_file[3].assign("charger/battery03");
        //battery_animation.user_animation_file[4].assign("charger/battery04");
        //battery_animation.user_animation_file[5].assign("charger/battery05");
        battery_animation.frames = user_animation_frames;
        battery_animation.num_frames = ARRAY_SIZE(user_animation_frames);
#else
        battery_animation.animation_file.assign("charger/battery_scale");
        battery_animation.frames = default_animation_frames;
        battery_animation.num_frames = ARRAY_SIZE(default_animation_frames);
#endif
    }
    if (battery_animation.fail_file.empty()) {
        // 未定義電池信息未知動畫,就采用默認的電池信息未知動畫
        battery_animation.fail_file.assign("charger/battery_fail");
    }

    // 輸出解析到的內容
    LOGV("Animation Description:\n");
    LOGV("  animation: %d %d '%s' (%d)\n",
         battery_animation.num_cycles, battery_animation.first_frame_repeats,
         battery_animation.animation_file.c_str(), battery_animation.num_frames);
    LOGV("  fail_file: '%s'\n", battery_animation.fail_file.c_str());
    LOGV("  clock: %d %d %d %d %d %d '%s'\n",
         battery_animation.text_clock.pos_x, battery_animation.text_clock.pos_y,
         battery_animation.text_clock.color_r, battery_animation.text_clock.color_g,
         battery_animation.text_clock.color_b, battery_animation.text_clock.color_a,
         battery_animation.text_clock.font_file.c_str());
    LOGV("  percent: %d %d %d %d %d %d '%s'\n",
         battery_animation.text_percent.pos_x, battery_animation.text_percent.pos_y,
         battery_animation.text_percent.color_r, battery_animation.text_percent.color_g,
         battery_animation.text_percent.color_b, battery_animation.text_percent.color_a,
         battery_animation.text_percent.font_file.c_str());

    // 輸出每幀動畫的信息,顯示時間,最低顯示電量和最高顯示電量
    for (int i = 0; i < battery_animation.num_frames; i++) {
        LOGV("  frame %.2d: %d %d %d\n", i, battery_animation.frames[i].disp_time,
             battery_animation.frames[i].min_level, battery_animation.frames[i].max_level);
    }

    return &battery_animation;
}

parse_animation_desc

// system/core/healthd/AnimationParser.cpp
bool parse_animation_desc(const std::string& content, animation* anim) {
    // 定義animation.txt文件可以解析的字段
    static constexpr const char* animation_prefix = "animation: ";
    static constexpr const char* fail_prefix = "fail: ";
    static constexpr const char* clock_prefix = "clock_display: ";
    static constexpr const char* percent_prefix = "percent_display: ";
    static constexpr const char* frame_prefix = "frame: ";

    // 把幀動畫 放入vector(動態數組)
    std::vector<animation::frame> frames;

	// for循環逐行解析
    // 以 animation: 3 2 charger/boot_charger_02 為例
    for (const auto& line : base::Split(content, "\n")) {
        animation::frame frame;
        const char* rest;

        // 跳過空行和'#'開頭的行
        if (can_ignore_line(line.c_str())) {
            continue;
            // remove_prefix會根據第二個參數是否符合,上面的例子剛好符合這一項
        	// 符合對應xxx_prefix后,會將"animation: "之后的內容,即"3 2 charger/boot_charger_02"
        	// 返回給rest,remove_prefix函數下面也會分析
        } else if (remove_prefix(line, animation_prefix, &rest)) {
            int start = 0, end = 0;
            // sscanf將3賦給anim->num_cycles,其余類似
            // 需要說明的是%n用於獲取sscanf到%n未知讀取的字符數,然后賦給start或者end
            // 這里"%n%*s%n"就是將"charger/boot_charger_02"字符串在line中的
            // 起始結束位置賦給start、end
            if (sscanf(rest, "%d %d %n%*s%n", &anim->num_cycles, &anim->first_frame_repeats,
                    &start, &end) != 2 ||
                end == 0) {
                LOGE("Bad animation format: %s\n", line.c_str());
                return false;
            } else {
                // 如果上面解析成功,就將"charger/boot_charger_02"賦給animation_file
                anim->animation_file.assign(&rest[start], end - start);
            }
            // 下面的內容類似
        } else if (remove_prefix(line, fail_prefix, &rest)) {
            anim->fail_file.assign(rest);
        } else if (remove_prefix(line, clock_prefix, &rest)) {
            if (!parse_text_field(rest, &anim->text_clock)) {
                LOGE("Bad clock_display format: %s\n", line.c_str());
                return false;
            }
        } else if (remove_prefix(line, percent_prefix, &rest)) {
            if (!parse_text_field(rest, &anim->text_percent)) {
                LOGE("Bad percent_display format: %s\n", line.c_str());
                return false;
            }
        } else if (sscanf(line.c_str(), " frame: %d %d %d",
                &frame.disp_time, &frame.min_level, &frame.max_level) == 3) {
            frames.push_back(std::move(frame));
        } else {
            LOGE("Malformed animation description line: %s\n", line.c_str());
            return false;
        }
    }

    if (anim->animation_file.empty() || frames.empty()) {
        LOGE("Bad animation description. Provide the 'animation: ' line and at least one 'frame: ' "
             "line.\n");
        return false;
    }

    anim->num_frames = frames.size();
    anim->frames = new animation::frame[frames.size()];
    std::copy(frames.begin(), frames.end(), anim->frames);

    return true;
}

因為公司使用的是將gif動畫切出來的一張張獨立的圖,不像默認動畫battery_scale.png圖那樣,一張圖包含所有的充電動畫surface信息,所以這里我們可以修改這個函數:

  • 在animation結構體里面的frame結構體中添加一個string類型的字段frame_file,用來記錄對應的frame文件名
  • 修改frame: 這個prefix的解析內容,添加frame文件的文件名字段解析,例如:frame: 50 16 20 charger/charging_animation_09,將最后的charger/charging_animation_09內容記錄到新添加的frame_file字段中
  • 根據frame_file指定的文件名,通過res_create_display_surface函數來為每一幀動畫圖創建對應的frame,因為animation的frames指針就是用來記錄動畫內容的,所以將創建的frame記錄到frames中即可。
  • 需要注意注釋掉healthd_mode_charger_init函數中通過charger/scale創建muti frame的代碼
remove_prefix
bool remove_prefix(const std::string& line, const char* prefix, const char** rest) {
    const char* str = line.c_str();
    int start;
    char c;
 
    // 經過解析后format為" animation: %n%c"
    std::string format = base::StringPrintf(" %s%%n%%c", prefix);
    // sscanf解析后,start為"animation: 3 2 charger/boot_charger_02"字符串中3的所在位置
    if (sscanf(str, format.c_str(), &start, &c) != 1) {
        return false;
    }
 
    // rest為"3 2 charger/boot_charger_02"字符串
    *rest = &str[start];
    return true;
}
open_png

到這里,動畫的初始化過程就基本講完了,不過還有一個需要注意的地方就是,在調用res_create_display_surface的時候會調用open_png函數來打開png圖片,在這個函數里面會判斷png圖片是否符合要求

會檢查位深

if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) {
        // 8-bit RGB images: great, nothing to do.
    } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) {
        // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
        png_set_expand_gray_1_2_4_to_8(*png_ptr);
    } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) {
        // paletted images: expand to 8-bit RGB.  Note that we DON'T
        // currently expand the tRNS chunk (if any) to an alpha
        // channel, because minui doesn't support alpha channels in
        // general.
        png_set_palette_to_rgb(*png_ptr);
        *channels = 3;
    } else {
        fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n",
                bit_depth, *channels, color_type);
        result = -7;
        goto exit;
    }

從上面一段代碼可以看出,animation動畫圖支持3通道的RGB圖,4通道的RGBA類型的圖是支持不了的,這里的A指的是Alpha通道;另外就是單通道灰階圖也是支持的。

與動畫有關的事件注冊與處理

healthd_register_event

事件通過這個接口注冊的,底層是epoll實現的。

// system/core/healthd/healthd.cpp

// int healthd_register_event(int fd, void (*handler)(uint32_t), EventWakeup wakeup = EVENT_NO_WAKEUP_FD);
int healthd_register_event(int fd, void (*handler)(uint32_t), EventWakeup wakeup) {
    struct epoll_event ev;

    ev.events = EPOLLIN;

    if (wakeup == EVENT_WAKEUP_FD)
        ev.events |= EPOLLWAKEUP;

    ev.data.ptr = (void *)handler;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        KLOG_ERROR(LOG_TAG,
                   "epoll_ctl failed; errno=%d\n", errno);
        return -1;
    }

    eventct++;
    return 0;
}

注冊的事件如下

healthd_mode_android.cpp:55:        if (healthd_register_event(gBinderFd, binder_event))
healthd.cpp:262:    if (healthd_register_event(uevent_fd, uevent_event, EVENT_WAKEUP_FD))
healthd.cpp:285:    if (healthd_register_event(wakealarm_fd, wakealarm_event, EVENT_WAKEUP_FD))
healthd_mode_charger.cpp:956:        healthd_register_event(epollfd, charger_event_handler, EVENT_WAKEUP_FD);

定時事件:wakealarm_event

if (healthd_register_event(wakealarm_fd, wakealarm_event, EVENT_WAKEUP_FD))

設置定時刷新電池電量的鬧鍾(默認10 min刷新一次)

static void wakealarm_event(uint32_t /*epevents*/) {
    unsigned long long wakeups;

    if (read(wakealarm_fd, &wakeups, sizeof(wakeups)) == -1) {
        KLOG_ERROR(LOG_TAG, "wakealarm_event: read wakealarm fd failed\n");
        return;
    }

    periodic_chores();
}

IPC通信:binder_event

和安卓系統各個部分進行相互通知,這里不展開了。

#define LOG_TAG "healthd-android"

#include <healthd/healthd.h>
#include "BatteryPropertiesRegistrar.h"

#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <cutils/klog.h>
#include <sys/epoll.h>

using namespace android;

static int gBinderFd;
static sp<BatteryPropertiesRegistrar> gBatteryPropertiesRegistrar;

void healthd_mode_android_battery_update(
    struct android::BatteryProperties *props) {
    if (gBatteryPropertiesRegistrar != NULL)
        gBatteryPropertiesRegistrar->notifyListeners(*props);

    return;
}

int healthd_mode_android_preparetowait(void) {
    IPCThreadState::self()->flushCommands();
    return -1;
}

static void binder_event(uint32_t /*epevents*/) {
    IPCThreadState::self()->handlePolledCommands();
}

void healthd_mode_android_init(struct healthd_config* /*config*/) {
    ProcessState::self()->setThreadPoolMaxThreadCount(0);
    IPCThreadState::self()->disableBackgroundScheduling(true);
    IPCThreadState::self()->setupPolling(&gBinderFd);

    if (gBinderFd >= 0) {
        if (healthd_register_event(gBinderFd, binder_event))
            KLOG_ERROR(LOG_TAG,
                       "Register for binder events failed\n");
    }

    gBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
    gBatteryPropertiesRegistrar->publish(gBatteryPropertiesRegistrar);
}

內核事件:uevent_event

監聽來自內核的供電子系統(POWER_SUPPLY)的事件

#define UEVENT_MSG_LEN 2048
static void uevent_event(uint32_t /*epevents*/) {
    char msg[UEVENT_MSG_LEN+2];
    char *cp;
    int n;

    n = uevent_kernel_multicast_recv(uevent_fd, msg, UEVENT_MSG_LEN);
    if (n <= 0)
        return;
    if (n >= UEVENT_MSG_LEN)   /* overflow -- discard */
        return;

    msg[n] = '\0';
    msg[n+1] = '\0';
    cp = msg;

    while (*cp) {
        if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) {
            healthd_battery_update();
            break;
        }

        /* advance to after the next \0 */
        while (*cp++)
            ;
    }
}

喚醒處理:charger_event_handler

由於是EVENT_WAKEUP_FD的事件,代表了能夠喚醒系統的輸入源(鍵(例如音量鍵,電源鍵));

向下分發了ev,處理用戶的輸入。

static void charger_event_handler(uint32_t /*epevents*/)
{
    int ret;

    ret = ev_wait(-1);
    if (!ret)
        ev_dispatch();
}

輸入事件處理:input_callback

例如音量鍵,電源鍵。

static int input_callback(int fd, unsigned int epevents, void *data)
{
    struct charger *charger = (struct charger *)data;
    struct input_event ev;
    int ret;

    ret = ev_get_input(fd, epevents, &ev);
    if (ret)
        return -1;
    update_input_state(charger, &ev);
    return 0;
}

input_callback是這樣子執行的。

注冊、分發:

void healthd_mode_charger_init(struct healthd_config* config)
{
    struct charger *charger = &charger_state;
    // ...
    // 創建了一個epoll
    ret = ev_init(input_callback, charger);
    if (!ret) {
        epollfd = ev_get_epollfd(); // 返回這個epoll的fd
        // 將這個epoll的fd放到了healthd里面的healthd_mainloop中的epoll中統一進行監聽
        healthd_register_event(epollfd, charger_event_handler, EVENT_WAKEUP_FD);
    }
}

static void charger_event_handler(uint32_t /*epevents*/)
{
    int ret;

    ret = ev_wait(-1);
    if (!ret)
        ev_dispatch();
}

// vendor/qcom/proprietary/fastmmi/libmmi/events.cpp
void ev_dispatch(void)
{
    int n;
    int ret;

    for (n = 0; n < npolledevents; n++) {
        struct fd_info *fdi = (fd_info *)(polledevents[n].data.ptr);
        ev_callback cb = fdi->cb;
        if (cb)
            cb(fdi->fd, polledevents[n].events, fdi->data);
    }
}

因此input_callback就通過這樣子的方式執行了:

charger_event_handler
	->ev_dispatch
		->input_callback
			->update_input_state
				->set_key_callback
update_input_state

獲取key的輸入事件,對其他類型的輸入事件不作處理。

static void update_input_state(struct charger *charger,
                               struct input_event *ev)
{
    if (ev->type != EV_KEY)
        return;
    set_key_callback(ev->code, ev->value, charger);
}
set_key_callback
static int set_key_callback(int code, int value, void *data)
{
    struct charger *charger = (struct charger *)data;
    int64_t now = curr_time_ms();
    int down = !!value;

    if (code > KEY_MAX)
        return -1;

    /* ignore events that don't modify our state */
    if (charger->keys[code].down == down)
        return 0;

    /* only record the down even timestamp, as the amount
     * of time the key spent not being pressed is not useful */
    if (down)
        charger->keys[code].timestamp = now;
    charger->keys[code].down = down;
    charger->keys[code].pending = true;
    if (down) {
        LOGV("[%" PRId64 "] key[%d] down\n", now, code);
    } else {
        int64_t duration = now - charger->keys[code].timestamp;
        int64_t secs = duration / 1000;
        int64_t msecs = duration - secs * 1000;
        LOGV("[%" PRId64 "] key[%d] up (was down for %" PRId64 ".%" PRId64 "sec)\n",
             now, code, secs, msecs);
    }

    return 0;
}

將按鍵的code碼和按下和抬起的狀態,pending狀態存入charger->keys中。

由於按鍵喚醒了healthd進程,處理完按鍵事件后,會再次調用healthd_mode_charger_heartbeat之后再進入poll_wait狀態

healthd_mode_charger_heartbeat

還記得嗎,在動畫的循環中會執行這個函數進行處理:

void healthd_mode_charger_heartbeat()
{
    struct charger *charger = &charger_state;
    int64_t now = curr_time_ms();
	// 處理接收進來的輸入事件
    handle_input_state(charger, now);
    handle_power_supply_state(charger, now);

    /* do screen update last in case any of the above want to start
     * screen transitions (animations, etc)
     */
    update_screen_state(charger, now);
}
handle_input_state
static void handle_input_state(struct charger *charger, int64_t now)
{
    // 處理按鍵,關鍵函數
    process_key(charger, KEY_POWER, now);
    process_key(charger, KEY_BACK, now);

    if (charger->next_key_check != -1 && now > charger->next_key_check)
        charger->next_key_check = -1;
}

調用process_key時,傳入了KEY_POWERKEY_BACK,代表需要處理的值,這個函數很重要,重點關注。

process_key

針對KEY_POWER的處理是這樣子的:

  • 1、如果上次電源鍵按下設置的2s超時時間到了,且當前的按鍵依然是按下狀態,那么判定用戶是希望重啟系統而不是點亮屏幕繼續保持關機充電。
  • 2、如果在上次按下電源鍵的2s時間內有抬起的動作,那么判定用戶的意圖只是希望看下當前充電的狀態(電量)
  • 3、如果用戶是在屏幕亮起的狀態下按了電源鍵,我們認為用戶是想要滅屏

針對KEY_BACK的處理與KEY_POWER類似,只不過少了針對第一點(用戶希望重啟系統而不是點亮屏幕)的重啟系統判斷

  • 1、如果在上次按下電源鍵的2s時間內有抬起的動作,那么判定用戶的意圖只是希望看下當前充電的狀態(電量)
  • 2、如果用戶是在屏幕亮起的狀態下按了電源鍵,我們認為用戶是想要滅屏
static void process_key(struct charger *charger, int code, int64_t now)
{
    struct animation *batt_anim = charger->batt_anim;
    struct key_state *key = &charger->keys[code];

    if (code == KEY_POWER) {
        if (key->down) {
            int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME;

            /*make sure backlight turn off before fb ready for display*
             *when press the power key */
            if (charger->charger_power_key == false) {
                healthd_board_mode_charger_set_backlight(false);
                gr_fb_blank(true);
                charger->charger_power_key = true;
            }

            // 如果上次電源鍵按下設置的2s超時時間到了,且當前的按鍵依然是按下狀態,
            // 那么我們判定用戶是希望重啟系統而不是點亮屏幕繼續保持關機充電。
            if (now >= reboot_timeout) {
                /* We do not currently support booting from charger mode on
                   all devices. Check the property and continue booting or reboot
                   accordingly. */
                if (property_get_bool("ro.enable_boot_charger_mode", false)) {
                    LOGW("[%" PRId64 "] booting from charger mode\n", now);
                    property_set("sys.boot_from_charger_mode", "1");
                } else {
                    if (charger->batt_anim->cur_level >= charger->boot_min_cap) {
                        LOGW("[%" PRId64 "] rebooting\n", now);
                        android_reboot(ANDROID_RB_RESTART, 0, 0);
                    } else {
                        LOGV("[%" PRId64 "] ignore power-button press, battery level "
                             "less than minimum\n", now);
                    }
                }
            } else {
                /* if the key is pressed but timeout hasn't expired,
                 * make sure we wake up at the right-ish time to check
                 */
                // 第一次按下按鍵會先進入這兒
                // 意義:更新poll_wait的超時時間為2s
                set_next_key_check(charger, key, POWER_ON_KEY_TIME);
            }
        } else {
            if (key->pending) {
                /* If key is pressed when the animation is not running, kick
                 * the animation and quite suspend; If key is pressed when
                 * the animation is running, turn off the animation and request
                 * suspend.
                 */
                if (!batt_anim->run) {
                    // 如果在上次按下電源鍵的2s時間內有抬起的動作,
                    // 那么判定用戶的意圖只是希望看下當前充電的狀態(電量)
                    kick_animation(batt_anim);
                    request_suspend(false);
                } else {
                    // 如果用戶是在屏幕亮起的狀態下按了電源鍵,我們認為用戶是想要滅屏
                    reset_animation(batt_anim);
                    charger->next_screen_transition = -1;
                    healthd_board_mode_charger_set_backlight(false);
                    gr_fb_blank(true);
                    if (charger->charger_connected)
                        request_suspend(true);
                }
            }
        }
    }
    else if(code == KEY_BACK)
    {
        if (key->down) {
            /*make sure backlight turn off before fb ready for display*
                    *when press the power key */
            if (charger->charger_power_key == false) {
                healthd_board_mode_charger_set_backlight(false);
                gr_fb_blank(true);
                healthd_board_mode_charger_set_backlight(true);
                charger->charger_power_key = true;
            }

        } else {
            if (key->pending) {
                /* If key is pressed when the animation is not running, kick
                        * the animation and quite suspend; If key is pressed when
                        * the animation is running, turn off the animation and request
                        * suspend.
                        */
                if (!batt_anim->run) {
                    kick_animation(batt_anim);
                    request_suspend(false);
                } else {
                    reset_animation(batt_anim);
                    charger->next_screen_transition = -1;
                    healthd_board_mode_charger_set_backlight(false);
                    gr_fb_blank(true);
                    if (charger->charger_connected)
                        request_suspend(true);
                }
            }
        }

    }

    key->pending = false;
}

只有在按鍵抬起的時候,batt_anim->run的狀態才會發生改變,kick_animation會將batt_anim->run置為true,從而使能動畫顯示。

之后在處理完本次按鍵事件后,會通過調用healthd_mode_ops->heartbeat()(即healthd_mode_charger_heartbeat)開啟動畫的顯示,隨后更新超時時間,進入三輪動畫的顯示流程。

內核主動通過uevent上報事件

uevent_event
	->healthd_battery_update
		->BatteryMonitor->update()
			->healthd_mode_charger_battery_update()

healthd_mode_charger_battery_update函數很簡單,只是簡單的將從內核獲取的prop信息傳遞給batt_prop

隨后代碼流程會回到healthd_mainloop中:

healthd_mainloop:
	healthd_mode_ops->heartbeat();
	mode_timeout = healthd_mode_ops->preparetowait();
	if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout))
    	timeout = mode_timeout;
    nevents = epoll_wait(epollfd, events, eventct, timeout);
    ...

顯然之后還會CALL到heartbeat,之后再更新epoll的超時時間heartbeat在關機充電的時候指向healthd_mode_charger_heartbeat

動畫的循環

main:
    periodic_chores();
    healthd_mode_ops->heartbeat();

healthd_mainloop

在主循環中,會定期去處理

// system/core/healthd/healthd.cpp
static void healthd_mainloop(void) {
    int nevents = 0;
    while (1) {
        struct epoll_event events[eventct];
        int timeout = awake_poll_interval;
        int mode_timeout;

        /* Don't wait for first timer timeout to run periodic chores */
        if (!nevents)
            periodic_chores();

        // 執行 healthd_mode_charger_heartbeat
        healthd_mode_ops->heartbeat();

        // 執行 healthd_mode_charger_preparetowait
        mode_timeout = healthd_mode_ops->preparetowait();
        if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout))
            timeout = mode_timeout;
        nevents = epoll_wait(epollfd, events, eventct, timeout);
        if (nevents == -1) {
            if (errno == EINTR)
                continue;
            KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failed\n");
            break;
        }

        // 執行通過healthd_register_event注冊過的事件
        for (int n = 0; n < nevents; ++n) {
            if (events[n].data.ptr)
                (*(void (*)(int))events[n].data.ptr)(events[n].events);
        }
    }

    return;
}

periodic_chores

static void periodic_chores() {
    healthd_battery_update();
}

直接調用了healthd_battery_update,那么繼續看下去

healthd_battery_update

調用

void healthd_battery_update(void) {
    // Fast wake interval when on charger (watch for overheat);
    // slow wake interval when on battery (watch for drained battery).

    // 執行了BatteryMonitor::update
   int new_wake_interval = gBatteryMonitor->update() ?
       healthd_config.periodic_chores_interval_fast :
           healthd_config.periodic_chores_interval_slow;

    if (new_wake_interval != wakealarm_wake_interval)
            wakealarm_set_interval(new_wake_interval);

    // During awake periods poll at fast rate.  If wake alarm is set at fast
    // rate then just use the alarm; if wake alarm is set at slow rate then
    // poll at fast rate while awake and let alarm wake up at slow rate when
    // asleep.

    if (healthd_config.periodic_chores_interval_fast == -1)
        awake_poll_interval = -1;
    else
        awake_poll_interval =
            new_wake_interval == healthd_config.periodic_chores_interval_fast ?
                -1 : healthd_config.periodic_chores_interval_fast * 1000;
}
插入USB進行關機充電會直接顯示三輪動畫

gBatteryMonitor->update()實際上會執行BatteryMonitor::update

從而導致healthd_mode_charger_battery_update的執行

healthd_mode_charger_battery_update
void healthd_mode_charger_battery_update(
    struct android::BatteryProperties *props)
{
    struct charger *charger = &charger_state;

    charger->charger_connected =
        props->chargerAcOnline || props->chargerUsbOnline ||
        props->chargerWirelessOnline;

	//第一次進入have_battery_state為false,之后一直為true
    if (!charger->have_battery_state) {
        charger->have_battery_state = true;
        charger->next_screen_transition = curr_time_ms() - 1;
        reset_animation(charger->batt_anim);
        kick_animation(charger->batt_anim);
    }
    batt_prop = props;
}

通過kick_animation使能動畫顯示anim->run = true;之后再進入healthd_mode_ops->heartbeat();從而進入動畫的顯示流程。

static void kick_animation(struct animation *anim)
{
    anim->run = true;
}

healthd_mode_charger_heartbeat

void healthd_mode_charger_heartbeat()
{
    struct charger *charger = &charger_state;
    int64_t now = curr_time_ms();
	// 處理接收進來的輸入事件
    handle_input_state(charger, now);
    handle_power_supply_state(charger, now);

    /* do screen update last in case any of the above want to start
     * screen transitions (animations, etc)
     */
    // 刷新屏幕顯示
    update_screen_state(charger, now);
}

如果是uevent事件,那么按鍵處理相關是不會觸發的,同時動畫相關的屏幕顯示其實也不會觸發。
原因在於batt_anim->run,並沒有被置1。那么這里還有一種情況就是在三輪動畫的顯示流程中,如果有來自於內核的uevent電源事件到來怎么處理。

healthd_mode_charger_preparetowait

int healthd_mode_charger_preparetowait(void)
{
    struct charger *charger = &charger_state;
    int64_t now = curr_time_ms();
    int64_t next_event = INT64_MAX;
    int64_t timeout;

    LOGV("[%" PRId64 "] next screen: %" PRId64 " next key: %" PRId64 " next pwr: %" PRId64 "\n", now,
         charger->next_screen_transition, charger->next_key_check,
         charger->next_pwr_check);

    if (charger->next_screen_transition != -1)
        next_event = charger->next_screen_transition;
    if (charger->next_key_check != -1 && charger->next_key_check < next_event)
        next_event = charger->next_key_check;
    if (charger->next_pwr_check != -1 && charger->next_pwr_check < next_event)
        next_event = charger->next_pwr_check;

    if (next_event != -1 && next_event != INT64_MAX)
        timeout = max(0, next_event - now);
    else
        timeout = -1;

   return (int)timeout;
}

動畫顯示過程

update_screen_state

動畫顯示無非就是判斷當前電池的狀態,然后選擇到對應的frame,再通過healthd_draw顯示到屏幕上面。

對於動畫,每輪顯示會進入update_screen_state多次,與下面的2個變量有關;

  • batt_anim->num_cycles :充電完整動畫重復顯示的次數
  • batt_anim->num_frames :每次充電完整動畫需要分解為幾次顯示

其中每顯示一幀,都會進入update_screen_state一次,最后滅屏進入suspend,細分的話有2種場景:

1、按鍵喚醒或者插入充電器的時候,當前電量沒到最后一個frame的范圍,那么連續顯示當前電量的frame到電量滿的frame動畫,並重復三次,其中每輪動畫中,當前電池電量對應的frame顯示1.5秒,除此之外的每個frame默認顯示時間為750ms。

2、按鍵喚醒或者插入充電器的時候,當前電量已經達到最后一個frame的范圍,那么顯示充電滿的frame,同樣連續顯示3次,每次1.5s。

// system/core/healthd/healthd_mode_charger.cpp
static void update_screen_state(struct charger *charger, int64_t now)
{
    struct animation *batt_anim = charger->batt_anim;
    int disp_time;

	/*
		1. batt_anim->run如果設置為false,則意味着不顯示動畫,就此返回
		2. 如果當前時間小於下一次動畫的觸發時間,就此返回
	*/
    /*  如果是uevent事件,那么按鍵處理相關是不會觸發的,同時動畫相關的屏幕顯示其實也不會觸發。
        原因在於batt_anim->run,並沒有被置1。
        那么這里還有一種情況:
        	在三輪動畫的顯示流程中,如果有來自於內核的uevent電源事件到來時。
        	由於當前時間小於下一次的frame顯示時間而返回
    */
    if (!batt_anim->run || now < charger->next_screen_transition) return;

    // 如下一段代碼主要是為了點亮屏幕,如果無法點亮屏幕就退出
    if (!minui_inited) {
        if (healthd_config && healthd_config->screen_on) {
            if (!healthd_config->screen_on(batt_prop)) {
                LOGV("[%" PRId64 "] leave screen off\n", now);
                batt_anim->run = false;
                charger->next_screen_transition = -1;
                if (charger->charger_connected)
                    request_suspend(true);
                return;
            }
        }
		//初始化顯示
        gr_init();
        //獲取字符顯示的長和寬
        gr_font_size(gr_sys_font(), &char_width, &char_height);
        init_status_display(batt_anim);

        #ifndef CHARGER_DISABLE_INIT_BLANK
        healthd_board_mode_charger_set_backlight(false);
        gr_fb_blank(true);
        #endif
        minui_inited = true;
    }

    /* animation is over, blank screen and leave */
    // 如果本輪顯示的次數達到了num_cycles(默認3次,4.5秒),則清屏,關背光,進入suspend
    if (batt_anim->num_cycles > 0 && batt_anim->cur_cycle == batt_anim->num_cycles) {
        reset_animation(batt_anim);
        charger->next_screen_transition = -1;
        charger->charger_power_key = false;
        healthd_board_mode_charger_set_backlight(false);
        gr_fb_blank(true);
        LOGV("[%" PRId64 "] animation done\n", now);
        if (charger->charger_connected)
            request_suspend(true);
        return;
    }

    // 獲取該幀顯示的時間,第一次進入,batt_anim->cur_frame是為0的
    // 取出第0幀的圖像需要顯示的時間(750ms)
    disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time;

    if (batt_anim->cur_cycle == 0 && batt_anim->cur_frame == 0)
        redraw_screen(charger);

    /* animation starting, set up the animation */
    // 動畫開始,根據讀到的電池百分比,選擇當前應該顯示的圖片,並重新計算距離下次顯示需要等待的最小時間間隔
    if (batt_anim->cur_frame == 0) {
        LOGV("[%" PRId64 "] animation starting\n", now);
        if (batt_prop) {
            // // 從battery service中獲取當前點亮和電池狀態
            batt_anim->cur_level = batt_prop->batteryLevel;
            batt_anim->cur_status = batt_prop->batteryStatus;
            if (batt_prop->batteryLevel >= 0 && batt_anim->num_frames != 0) {
                /* find first frame given current battery level */
                // 找到第一張符合對應電量要求的幀
                for (int i = 0; i < batt_anim->num_frames; i++) {
                    if (batt_anim->cur_level >= batt_anim->frames[i].min_level &&
                        batt_anim->cur_level <= batt_anim->frames[i].max_level) {
                        batt_anim->cur_frame = i;
                        break;
                    }
                }

                LOGE("animation starting cur_frame =%d \n", batt_anim->cur_frame);

                // repeat the first frame first_frame_repeats times
                // 動畫第一幀可以設置重復顯示次數,所以需要更新第一幀顯示的時間
                disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time *
                    batt_anim->first_frame_repeats;
            }
        }
    }

    /* unblank the screen on first cycle */
    // 如果是本輪顯示的第一次,則顯示屏幕內容點亮背光燈
    if (batt_anim->cur_cycle == 0) {
        gr_fb_blank(false);
        gr_flip();
        healthd_board_mode_charger_set_backlight(true);
    }

    /* draw the new frame (@ cur_frame) */
    //開始顯示動畫
    redraw_screen(charger);

    /* if we don't have anim frames, we only have one image, so just bump
     * the cycle counter and exit
     */
    // 如果沒有動畫資源,則自增圈數,更新距離下次顯示的最小時間間隔,然后就此返回
    if (batt_anim->num_frames == 0 || batt_anim->cur_level < 0) {
        LOGW("[%" PRId64 "] animation missing or unknown battery status\n", now);
        charger->next_screen_transition = now + BATTERY_UNKNOWN_TIME;
        batt_anim->cur_cycle++;
        return;
    }

    /* schedule next screen transition */
    // 計算下一張動畫的切換時間
    charger->next_screen_transition = now + disp_time;

    /* advance frame cntr to the next valid frame only if we are charging
     * if necessary, advance cycle cntr, and reset frame cntr
     */
    // 下面的邏輯是:只要充電的電量沒有達到最后一個frame的階段,那么直接取出下一次顯示需要的frame用於顯示。
    // 如果還插着充電器
    if (charger->charger_connected) {

        LOGE("update screen state cur cycle=%d \n", batt_anim->cur_cycle);
        LOGE("update screen state cur_frame=%d \n", batt_anim->cur_frame);
        LOGE("update screen state cur_level=%d \n", batt_anim->cur_level);
        
        // 記錄下一幀動畫的索引
        batt_anim->cur_cycle++;


        /* don't reset the cycle counter, since we use that as a signal
             * in a test above to check if animation is over*/
        // 如果當前的電量已經沒有落在下一幀中,那么需要調整下一幀的動畫到合理的位置
        while (batt_anim->cur_frame < batt_anim->num_frames &&
               (batt_anim->cur_level < batt_anim->frames[batt_anim->cur_frame].min_level ||
                batt_anim->cur_level > batt_anim->frames[batt_anim->cur_frame].max_level)) {
            batt_anim->cur_frame++;
        }
        // 如果當前已經是最后一幀,那么將下一幀重置為0
        if (batt_anim->cur_frame >= batt_anim->num_frames) {
            batt_anim->cur_cycle++;
            batt_anim->cur_frame = 0;

        }


    } else {
        /* Stop animating if we're not charging.
         * If we stop it immediately instead of going through this loop, then
         * the animation would stop somewhere in the middle.
         */
        // 拔出了充電器,重置變量
        batt_anim->cur_frame = 0;
        batt_anim->cur_cycle++;
    }
}

redraw_screen

// system/core/healthd/healthd_mode_charger.cpp
static void redraw_screen(struct charger *charger)
{
    struct animation *batt_anim = charger->batt_anim;

    clear_screen();

    /* try to display *something* */
    // 首先判斷電池狀態和動畫內容是否有問題
    if (batt_anim->cur_level < 0 || batt_anim->num_frames == 0)
        draw_unknown(charger); // 有問題則顯示battery_fail動畫
    else
        draw_battery(charger); // 否則,顯示電池充電動畫
    // 刷新屏幕
    gr_flip();
}

static void draw_battery(const struct charger* charger)
{
    const struct animation& anim = *charger->batt_anim;
    const struct animation::frame& frame = anim.frames[anim.cur_frame];

    if (anim.num_frames != 0) {
        // 如函數名,將動畫顯示到屏幕中間
        draw_surface_centered(frame.surface);
        LOGV("drawing frame #%d min_cap=%d time=%d\n",
             anim.cur_frame, frame.min_level,
             frame.disp_time);
    }
    // 如果有設置顯示時鍾和電池百分百,則同時描繪
    draw_clock(anim);
    draw_percent(anim);
}

所以,動畫的連續顯示也顯得比較簡單。如果想要做修改,理清顯示流程即可。

另外,看上面的流程可以知道,如果需要顯示電量百分比,可以提供對應的font字體文件,當然更簡單的就是提供110和百分號的png圖,然后按照上面解析動畫的流程一樣,將百分比對應的圖片也加載進來,最后顯示即可。

附錄:關機充電時,按下按鍵的有關log

[ 1191.430816] qpnp_kpdpwr_irq into!
[ 1191.434579] PMIC input: code=116, sts=0x0
[ 1191.440183] healthd: void healthd_mainloop():epoll_wait is up
[ 1191.447273] charger: int input_callback(int, unsigned int, void*) into!
[ 1191.455508] healthd: void healthd_mainloop() ->heartbeat()
[ 1191.462325] charger: void healthd_mode_charger_heartbeat() into!
[ 1191.469792] charger: void process_key(charger*, int, int64_t) into! code=116
[ 1191.478251] charger: void process_key(charger*, int, int64_t) into! key->down=0
[ 1191.487067] charger: void process_key(charger*, int, int64_t) key->pending=1
[ 1191.495594] charger: void process_key(charger*, int, int64_t) !batt_anim->run
[ 1191.504184] charger: void update_screen_state(charger*, int64_t) into!
[ 1191.512167] charger: void update_screen_state(charger*, int64_t) into!  gr_fb_blank(false)

do_fb_ioctl ->fb_blank->fb_notifier_call_chain ->fb_notifier_callback

附錄:ev_init、ev_get_input有關的庫

在高通fastmmi中用到的一個用來處理輸入事件的庫events,本質是也是一個epoll_ctl

int ev_init(ev_callback input_cb, void *data)
{
    DIR *dir;
    struct dirent *de;
    int fd;
    struct epoll_event ev;
    bool epollctlfail = false;

    epollfd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
    if (epollfd == -1)
        return -1;

    dir = opendir("/dev/input");
    if(dir != 0) {
        while((de = readdir(dir))) {
            unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];

//            fprintf(stderr,"/dev/input/%s\n", de->d_name);
            if(strncmp(de->d_name,"event",5)) continue;
            fd = openat(dirfd(dir), de->d_name, O_RDONLY);
            if(fd < 0) continue;

            /* read the evbits of the input device */
            if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0) {
                close(fd);
                continue;
            }

            /* TODO: add ability to specify event masks. For now, just assume
             * that only EV_KEY and EV_REL event types are ever needed. */
            if(!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
                close(fd);
                continue;
            } else if(test_bit(EV_ABS, ev_bits) && test_bit(EV_REL, ev_bits)) {
                close(fd);
                continue;
            }

            ev.events = EPOLLIN | EPOLLWAKEUP;
            ev.data.ptr = (void *)&ev_fdinfo[ev_count];
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev)) {
                close(fd);
                epollctlfail = true;
                continue;
            }

            ev_fdinfo[ev_count].fd = fd;
            ev_fdinfo[ev_count].cb = input_cb;
            ev_fdinfo[ev_count].data = data;
            ev_count++;
            ev_dev_count++;
            if(ev_dev_count == MAX_DEVICES) break;
        }
    }
    if (epollctlfail && !ev_count) {
        close(epollfd);
        epollfd = -1;
        return -1;
    }

    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM