diff --git a/BoardConfig.mk b/BoardConfig.mk index ef3347e..7927d81 100644 --- a/BoardConfig.mk +++ b/BoardConfig.mk @@ -54,6 +54,11 @@ BOARD_KERNEL_BASE := 0x00000000 BOARD_KERNEL_PAGESIZE := 2048 BOARD_MKBOOTIMG_ARGS := --ramdisk_offset 0x01000000 --tags_offset 0x00000100 +# Offmode Charging +COMMON_GLOBAL_CFLAGS += \ + -DBOARD_CHARGING_CMDLINE_NAME='"androidboot.mode"' \ + -DBOARD_CHARGING_CMDLINE_VALUE='"chargerlogo"' + # Enable dex-preoptimization to speed up first boot sequence WITH_DEXPREOPT := true diff --git a/charger/Android.mk b/charger/Android.mk new file mode 100644 index 0000000..98bcaa8 --- /dev/null +++ b/charger/Android.mk @@ -0,0 +1,54 @@ +# Copyright 2011 The Android Open Source Project + +ifneq ($(BUILD_TINY_ANDROID),true) + +LOCAL_PATH := $(call my-dir) + +define _add-w7-charger-image +include $$(CLEAR_VARS) +LOCAL_MODULE := device_w7_w7_charger_$(notdir $(1)) +LOCAL_MODULE_STEM := $(notdir $(1)) +_img_modules += $$(LOCAL_MODULE) +LOCAL_SRC_FILES := $1 +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $$(TARGET_ROOT_OUT)/res/images/charger +include $$(BUILD_PREBUILT) +endef + +_img_modules := +_images := +$(foreach _img, $(call find-subdir-subdir-files, "images", "*.png"), \ + $(eval $(call _add-w7-charger-image,$(_img)))) + +include $(CLEAR_VARS) +LOCAL_MODULE := charger_res_images_w7 +LOCAL_MODULE_TAGS := optional +LOCAL_REQUIRED_MODULES := $(_img_modules) +include $(BUILD_PHONY_PACKAGE) + +_add-charger-image := +_img_modules := + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + charger.c + +LOCAL_CFLAGS += -DCHARGER_ENABLE_SUSPEND + +LOCAL_MODULE := charger_w7 +LOCAL_MODULE_TAGS := optional +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT_SBIN) +LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_SBIN_UNSTRIPPED) +LOCAL_ADDITIONAL_DEPENDENCIES := charger_res_images_w7 +LOCAL_C_INCLUDES := $(call project-path-for,recovery) + +LOCAL_STATIC_LIBRARIES := libminui libpixelflinger_static libpng +LOCAL_STATIC_LIBRARIES += libsuspend +LOCAL_STATIC_LIBRARIES += libz libstdc++ libcutils liblog libm libc + +include $(BUILD_EXECUTABLE) + +endif diff --git a/charger/charger.c b/charger/charger.c new file mode 100644 index 0000000..18c2d56 --- /dev/null +++ b/charger/charger.c @@ -0,0 +1,1281 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define DEBUG_UEVENTS +#define CHARGER_KLOG_LEVEL 6 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef CHARGER_ENABLE_SUSPEND +#include +#endif + +#include "minui/minui.h" + +#include +#include +#include + +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +#define MSEC_PER_SEC (1000LL) +#define NSEC_PER_MSEC (1000000LL) + +#define BATTERY_UNKNOWN_TIME (2 * MSEC_PER_SEC) +#define POWER_ON_KEY_TIME (2 * MSEC_PER_SEC) +#define UNPLUGGED_SHUTDOWN_TIME (2 * MSEC_PER_SEC) + +#define BATTERY_FULL_THRESH 95 + +#define BACKLIGHT_TOGGLE_PATH "/sys/class/leds/lcd-backlight/brightness" + +#define LAST_KMSG_PATH "/proc/last_kmsg" +#define LAST_KMSG_MAX_SZ (32 * 1024) + +#if 1 +#define LOGE(x...) do { KLOG_ERROR("charger", x); } while (0) +#define LOGI(x...) do { KLOG_INFO("charger", x); } while (0) +#define LOGV(x...) do { KLOG_DEBUG("charger", x); } while (0) +#else +#define LOG_NDEBUG 0 +#define LOG_TAG "charger" +#include +#endif + +#define SYS_POWER_STATE "/sys/power/state" + +struct key_state { + bool pending; + bool down; + int64_t timestamp; +}; + +struct power_supply { + struct listnode list; + char name[256]; + char type[32]; + bool online; + bool valid; + char cap_path[PATH_MAX]; +}; + +struct frame { + const char *name; + int disp_time; + int min_capacity; + bool level_only; + + gr_surface surface; +}; + +struct animation { + bool run; + + struct frame *frames; + int cur_frame; + int num_frames; + + int cur_cycle; + int num_cycles; + + /* current capacity being animated */ + int capacity; +}; + +struct charger { + int64_t next_screen_transition; + int64_t next_key_check; + int64_t next_pwr_check; + + struct key_state keys[KEY_MAX + 1]; + int uevent_fd; + + struct listnode supplies; + int num_supplies; + int num_supplies_online; + + struct animation *batt_anim; + gr_surface surf_unknown; + + struct power_supply *battery; +}; + +struct uevent { + const char *action; + const char *path; + const char *subsystem; + const char *ps_name; + const char *ps_type; + const char *ps_online; +}; + +static struct frame batt_anim_frames[] = { + { + .name = "charger/battery_0", + .disp_time = 750, + .min_capacity = 0, + }, + { + .name = "charger/battery_1", + .disp_time = 750, + .min_capacity = 15, + }, + { + .name = "charger/battery_2", + .disp_time = 750, + .min_capacity = 35, + }, + { + .name = "charger/battery_3", + .disp_time = 750, + .min_capacity = 55, + }, + { + .name = "charger/battery_4", + .disp_time = 750, + .min_capacity = 75, + }, + { + .name = "charger/battery_5", + .disp_time = 750, + .min_capacity = 95, + }, +}; + +static struct animation battery_animation = { + .frames = batt_anim_frames, + .num_frames = ARRAY_SIZE(batt_anim_frames), + .num_cycles = 5, +}; + +static struct charger charger_state = { + .batt_anim = &battery_animation, +}; + +static int char_width; +static int char_height; + +/*On certain targets the FBIOBLANK ioctl does not turn off the + * backlight. In those cases we need to manually toggle it on/off + */ +static int set_backlight(int toggle) +{ + int fd; + char buffer[10]; + + memset(buffer, '\0', sizeof(buffer)); + fd = open(BACKLIGHT_TOGGLE_PATH, O_RDWR); + if (fd < 0) { + LOGE("Could not open backlight node : %s", strerror(errno)); + goto cleanup; + } + if (toggle) { + LOGI("Enabling backlight"); + snprintf(buffer, sizeof(int), "%d\n", 255); + } else { + LOGI("Disabling backlight"); + snprintf(buffer, sizeof(int), "%d\n", 100); + } + if (write(fd, buffer,strlen(buffer)) < 0) { + LOGE("Could not write to backlight node : %s", strerror(errno)); + goto cleanup; + } +cleanup: + if (fd >= 0) + close(fd); + return 0; +} +/* current time in milliseconds */ +static int64_t curr_time_ms(void) +{ + struct timespec tm; + clock_gettime(CLOCK_MONOTONIC, &tm); + return tm.tv_sec * MSEC_PER_SEC + (tm.tv_nsec / NSEC_PER_MSEC); +} + +static void clear_screen(void) +{ + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); +}; + +#define MAX_KLOG_WRITE_BUF_SZ 256 + +static void dump_last_kmsg(void) +{ + char *buf; + char *ptr; + unsigned sz = 0; + int len; + + LOGI("\n"); + LOGI("*************** LAST KMSG ***************\n"); + LOGI("\n"); + buf = load_file(LAST_KMSG_PATH, &sz); + if (!buf || !sz) { + LOGI("last_kmsg not found. Cold reset?\n"); + goto out; + } + + len = min(sz, LAST_KMSG_MAX_SZ); + ptr = buf + (sz - len); + + while (len > 0) { + int cnt = min(len, MAX_KLOG_WRITE_BUF_SZ); + char yoink; + char *nl; + + nl = memrchr(ptr, '\n', cnt - 1); + if (nl) + cnt = nl - ptr + 1; + + yoink = ptr[cnt]; + ptr[cnt] = '\0'; + klog_write(6, "<6>%s", ptr); + ptr[cnt] = yoink; + + len -= cnt; + ptr += cnt; + } + + free(buf); + +out: + LOGI("\n"); + LOGI("************* END LAST KMSG *************\n"); + LOGI("\n"); +} + +static int read_file(const char *path, char *buf, size_t sz) +{ + int fd; + size_t cnt; + + fd = open(path, O_RDONLY, 0); + if (fd < 0) + goto err; + + cnt = read(fd, buf, sz - 1); + if (cnt <= 0) + goto err; + buf[cnt] = '\0'; + if (buf[cnt - 1] == '\n') { + cnt--; + buf[cnt] = '\0'; + } + + close(fd); + return cnt; + +err: + if (fd >= 0) + close(fd); + return -1; +} + +static int read_file_int(const char *path, int *val) +{ + char buf[32]; + int ret; + int tmp; + char *end; + + ret = read_file(path, buf, sizeof(buf)); + if (ret < 0) + return -1; + + tmp = strtol(buf, &end, 0); + if (end == buf || + ((end < buf+sizeof(buf)) && (*end != '\n' && *end != '\0'))) + goto err; + + *val = tmp; + return 0; + +err: + return -1; +} + +static int write_file(const char *path, char *buf, size_t sz) +{ + int fd; + size_t cnt; + + fd = open(path, O_WRONLY, 0); + if (fd < 0) + goto err; + + cnt = write(fd, buf, sz); + if (cnt <= 0) + goto err; + + close(fd); + return cnt; + +err: + if (fd >= 0) + close(fd); + return -1; +} + +static int get_battery_capacity(struct charger *charger) +{ + int ret; + int batt_cap = -1; + + if (!charger->battery) + return -1; + + ret = read_file_int(charger->battery->cap_path, &batt_cap); + if (ret < 0 || batt_cap > 100) { + batt_cap = -1; + } + + return batt_cap; +} + +static struct power_supply *find_supply(struct charger *charger, + const char *name) +{ + struct listnode *node; + struct power_supply *supply; + + list_for_each(node, &charger->supplies) { + supply = node_to_item(node, struct power_supply, list); + if (!strncmp(name, supply->name, sizeof(supply->name))) + return supply; + } + return NULL; +} + +static struct power_supply *add_supply(struct charger *charger, + const char *name, const char *type, + const char *path, bool online) +{ + struct power_supply *supply; + + supply = calloc(1, sizeof(struct power_supply)); + if (!supply) + return NULL; + + strlcpy(supply->name, name, sizeof(supply->name)); + strlcpy(supply->type, type, sizeof(supply->type)); + snprintf(supply->cap_path, sizeof(supply->cap_path), + "/sys/%s/capacity", path); + supply->online = online; + list_add_tail(&charger->supplies, &supply->list); + charger->num_supplies++; + LOGV("... added %s %s %d\n", supply->name, supply->type, online); + return supply; +} + +static void remove_supply(struct charger *charger, struct power_supply *supply) +{ + if (!supply) + return; + list_remove(&supply->list); + charger->num_supplies--; + free(supply); +} + +#ifdef CHARGER_ENABLE_SUSPEND +static int request_suspend(bool enable) +{ + if (enable) + return autosuspend_enable(); + else + return autosuspend_disable(); +} +#else +static int request_suspend(bool enable) +{ + return 0; +} +#endif + +static void parse_uevent(const char *msg, struct uevent *uevent) +{ + uevent->action = ""; + uevent->path = ""; + uevent->subsystem = ""; + uevent->ps_name = ""; + uevent->ps_online = ""; + uevent->ps_type = ""; + + /* currently ignoring SEQNUM */ + while (*msg) { +#ifdef DEBUG_UEVENTS + LOGV("uevent str: %s\n", msg); +#endif + if (!strncmp(msg, "ACTION=", 7)) { + msg += 7; + uevent->action = msg; + } else if (!strncmp(msg, "DEVPATH=", 8)) { + msg += 8; + uevent->path = msg; + } else if (!strncmp(msg, "SUBSYSTEM=", 10)) { + msg += 10; + uevent->subsystem = msg; + } else if (!strncmp(msg, "POWER_SUPPLY_NAME=", 18)) { + msg += 18; + uevent->ps_name = msg; + } else if (!strncmp(msg, "POWER_SUPPLY_ONLINE=", 20)) { + msg += 20; + uevent->ps_online = msg; + } else if (!strncmp(msg, "POWER_SUPPLY_TYPE=", 18)) { + msg += 18; + uevent->ps_type = msg; + } + + /* advance to after the next \0 */ + while (*msg++) + ; + } + + LOGV("event { '%s', '%s', '%s', '%s', '%s', '%s' }\n", + uevent->action, uevent->path, uevent->subsystem, + uevent->ps_name, uevent->ps_type, uevent->ps_online); +} + +static void process_ps_uevent(struct charger *charger, struct uevent *uevent) +{ + int online; + char ps_type[32]; + struct power_supply *supply = NULL; + int i; + bool was_online = false; + bool battery = false; + + if (uevent->ps_type[0] == '\0') { + char *path; + int ret; + + if (uevent->path[0] == '\0') + return; + ret = asprintf(&path, "/sys/%s/type", uevent->path); + if (ret <= 0) + return; + ret = read_file(path, ps_type, sizeof(ps_type)); + free(path); + if (ret < 0) + return; + } else { + strlcpy(ps_type, uevent->ps_type, sizeof(ps_type)); + } + + if (!strncmp(ps_type, "Battery", 7)) + battery = true; + + online = atoi(uevent->ps_online); + supply = find_supply(charger, uevent->ps_name); + if (supply) { + was_online = supply->online; + supply->online = online; + } + + if (!strcmp(uevent->action, "add")) { + if (!supply) { + supply = add_supply(charger, uevent->ps_name, ps_type, uevent->path, + online); + if (!supply) { + LOGE("cannot add supply '%s' (%s %d)\n", uevent->ps_name, + uevent->ps_type, online); + return; + } + /* only pick up the first battery for now */ + if (battery && !charger->battery) + charger->battery = supply; + } else { + LOGE("supply '%s' already exists..\n", uevent->ps_name); + } + } else if (!strcmp(uevent->action, "remove")) { + if (supply) { + if (charger->battery == supply) + charger->battery = NULL; + remove_supply(charger, supply); + supply = NULL; + } + } else if (!strcmp(uevent->action, "change")) { + if (!supply) { + LOGE("power supply '%s' not found ('%s' %d)\n", + uevent->ps_name, ps_type, online); + } + } else { + return; + } + + /* allow battery to be managed in the supply list but make it not + * contribute to online power supplies. */ + if (!battery) { + if (was_online && !online) + charger->num_supplies_online--; + else if (supply && !was_online && online) + charger->num_supplies_online++; + } + LOGI("power supply %s (%s) %s (action=%s num_online=%d num_supplies=%d)\n", + uevent->ps_name, ps_type, battery ? "" : online ? "online" : "offline", + uevent->action, charger->num_supplies_online, charger->num_supplies); +} + +static void process_uevent(struct charger *charger, struct uevent *uevent) +{ + if (!strcmp(uevent->subsystem, "power_supply")) + process_ps_uevent(charger, uevent); +} + +#define UEVENT_MSG_LEN 1024 +static int handle_uevent_fd(struct charger *charger, int fd) +{ + char msg[UEVENT_MSG_LEN+2]; + int n; + + if (fd < 0) + return -1; + + while (true) { + struct uevent uevent; + + n = uevent_kernel_multicast_recv(fd, msg, UEVENT_MSG_LEN); + if (n <= 0) + break; + if (n >= UEVENT_MSG_LEN) /* overflow -- discard */ + continue; + + msg[n] = '\0'; + msg[n+1] = '\0'; + + parse_uevent(msg, &uevent); + process_uevent(charger, &uevent); + } + + return 0; +} + +static int uevent_callback(int fd, short revents, void *data) +{ + struct charger *charger = data; + + if (!(revents & POLLIN)) + return -1; + return handle_uevent_fd(charger, fd); +} + +/* force the kernel to regenerate the change events for the existing + * devices, if valid */ +static void do_coldboot(struct charger *charger, DIR *d, const char *event, + bool follow_links, int max_depth) +{ + struct dirent *de; + int dfd, fd; + + dfd = dirfd(d); + + fd = openat(dfd, "uevent", O_WRONLY); + if (fd >= 0) { + write(fd, event, strlen(event)); + close(fd); + handle_uevent_fd(charger, charger->uevent_fd); + } + + while ((de = readdir(d)) && max_depth > 0) { + DIR *d2; + + LOGV("looking at '%s'\n", de->d_name); + + if ((de->d_type != DT_DIR && !(de->d_type == DT_LNK && follow_links)) || + de->d_name[0] == '.') { + LOGV("skipping '%s' type %d (depth=%d follow=%d)\n", + de->d_name, de->d_type, max_depth, follow_links); + continue; + } + LOGV("can descend into '%s'\n", de->d_name); + + fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY); + if (fd < 0) { + LOGE("cannot openat %d '%s' (%d: %s)\n", dfd, de->d_name, + errno, strerror(errno)); + continue; + } + + d2 = fdopendir(fd); + if (d2 == 0) + close(fd); + else { + LOGV("opened '%s'\n", de->d_name); + do_coldboot(charger, d2, event, follow_links, max_depth - 1); + closedir(d2); + } + } +} + +static void coldboot(struct charger *charger, const char *path, + const char *event) +{ + char str[256]; + + LOGV("doing coldboot '%s' in '%s'\n", event, path); + DIR *d = opendir(path); + if (d) { + snprintf(str, sizeof(str), "%s\n", event); + do_coldboot(charger, d, str, true, 1); + closedir(d); + } +} + +static int draw_text(const char *str, int x, int y) +{ + int str_len_px = gr_measure(str); + + if (x < 0) + x = (gr_fb_width() - str_len_px) / 2; + if (y < 0) + y = 455; + gr_text(x, y, str, 1); + + return y + char_height; +} + +static void draw_capacity(struct charger *charger) +{ + char cap_str[64]; + int x, y; + int str_len_px; + + snprintf(cap_str, sizeof(cap_str), "%d%%", charger->batt_anim->capacity); + str_len_px = gr_measure(cap_str); + x = (gr_fb_width() - str_len_px) / 2; + y = 442; + gr_color(0xff, 0xff, 0xff, 255); + gr_text(x, y, cap_str, 0); +} + +/* returns the last y-offset of where the surface ends */ +static int draw_surface_centered(struct charger *charger, gr_surface surface) +{ + int w; + int h; + int x; + int y; + + w = gr_get_width(surface); + h = gr_get_height(surface); + x = (gr_fb_width() - w) / 2 ; + y = (gr_fb_height() - h) / 2 ; + + LOGV("drawing surface %dx%d+%d+%d\n", w, h, x, y); + gr_blit(surface, 0, 0, w, h, x, y); + return y + h; +} + +static void draw_unknown(struct charger *charger) +{ + int y; + if (charger->surf_unknown) { + draw_surface_centered(charger, charger->surf_unknown); + } else { + gr_color(0xff, 0xff, 0xff, 255); + y = draw_text("Charging!", -1, -1); + draw_text("?\?/100", -1, y + 25); + } +} + +static void draw_battery(struct charger *charger) +{ + struct animation *batt_anim = charger->batt_anim; + struct frame *frame = &batt_anim->frames[batt_anim->cur_frame]; + + if (batt_anim->num_frames != 0) { + draw_surface_centered(charger, frame->surface); + LOGV("drawing frame #%d name=%s min_cap=%d time=%d\n", + batt_anim->cur_frame, frame->name, frame->min_capacity, + frame->disp_time); + } +} + +static void redraw_screen(struct charger *charger) +{ + struct animation *batt_anim = charger->batt_anim; + + clear_screen(); + + /* try to display *something* */ + if (batt_anim->capacity < 0 || batt_anim->num_frames == 0) + draw_unknown(charger); + else { + draw_battery(charger); + draw_capacity(charger); + } + gr_flip(); +} + +static void kick_animation(struct animation *anim) +{ + anim->run = true; +} + +static void reset_animation(struct animation *anim) +{ + anim->cur_cycle = 0; + anim->cur_frame = 0; + anim->run = false; +} + +static void update_screen_state(struct charger *charger, int64_t now) +{ + struct animation *batt_anim = charger->batt_anim; + int cur_frame; + int disp_time; + + if (!batt_anim->run || now < charger->next_screen_transition) + return; + + /* animation is over, blank screen and leave */ + if (batt_anim->cur_cycle == batt_anim->num_cycles) { + reset_animation(batt_anim); + charger->next_screen_transition = -1; + set_backlight(false); + gr_fb_blank(true); + + LOGV("[%lld] animation done\n", now); + if (charger->num_supplies_online > 0) + request_suspend(true); + return; + } + + disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time; + + /* animation starting, set up the animation */ + if (batt_anim->cur_frame == 0) { + int batt_cap; + int ret; + + LOGV("[%lld] animation starting\n", now); + batt_cap = get_battery_capacity(charger); + if (batt_cap >= 0 && batt_anim->num_frames != 0) { + int i; + + /* find first frame given current capacity */ + for (i = 1; i < batt_anim->num_frames; i++) { + if (batt_cap < batt_anim->frames[i].min_capacity) + break; + } + batt_anim->cur_frame = i - 1; + + /* show the first frame for twice as long */ + disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time * 2; + } + + batt_anim->capacity = batt_cap; + } + + /* unblank the screen on first cycle */ + if (batt_anim->cur_cycle == 0) { + gr_fb_blank(false); + 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->capacity < 0) { + LOGV("[%lld] 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 + * if necessary, advance cycle cntr, and reset frame cntr + */ + batt_anim->cur_frame++; + + /* if the frame is used for level-only, that is only show it when it's + * the current level, skip it during the animation. + */ + while (batt_anim->cur_frame < batt_anim->num_frames && + batt_anim->frames[batt_anim->cur_frame].level_only) + batt_anim->cur_frame++; + if (batt_anim->cur_frame >= batt_anim->num_frames) { + batt_anim->cur_cycle++; + batt_anim->cur_frame = 0; + + /* don't reset the cycle counter, since we use that as a signal + * in a test above to check if animation is over + */ + } +} + +static int set_key_callback(int code, int value, void *data) +{ + struct charger *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("[%lld] 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("[%lld] key[%d] up (was down for %lld.%lldsec)\n", now, + code, secs, msecs); + } + + return 0; +} + +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); +} + +static void set_next_key_check(struct charger *charger, + struct key_state *key, + int64_t timeout) +{ + int64_t then = key->timestamp + timeout; + + if (charger->next_key_check == -1 || then < charger->next_key_check) + charger->next_key_check = then; +} + +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]; + int64_t next_key_check; + + if (code == KEY_POWER) { + if (key->down) { + int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME; + if (now >= reboot_timeout) { + LOGI("[%lld] rebooting\n", now); + android_reboot(ANDROID_RB_RESTART, 0, 0); + } else { + /* if the key is pressed but timeout hasn't expired, + * make sure we wake up at the right-ish time to check + */ + set_next_key_check(charger, key, POWER_ON_KEY_TIME); + } + } else { + /* if the power key got released, force screen state cycle */ + if (key->pending) { + if (!batt_anim->run) { + request_suspend(false); + kick_animation(charger->batt_anim); + } else { + reset_animation(batt_anim); + charger->next_screen_transition = -1; + gr_fb_blank(true); + set_backlight(false); + if (charger->num_supplies_online > 0) + request_suspend(true); + } + } + } + } else { + if (key->pending) { + request_suspend(false); + kick_animation(charger->batt_anim); + } + } + + key->pending = false; +} + +static void handle_input_state(struct charger *charger, int64_t now) +{ + process_key(charger, KEY_POWER, now); + process_key(charger, KEY_HOME, now); + process_key(charger, KEY_HOMEPAGE, now); + + if (charger->next_key_check != -1 && now > charger->next_key_check) + charger->next_key_check = -1; +} + +static void handle_power_supply_state(struct charger *charger, int64_t now) +{ + if (charger->num_supplies_online == 0) { + request_suspend(false); + if (charger->next_pwr_check == -1) { + charger->next_pwr_check = now + UNPLUGGED_SHUTDOWN_TIME; + LOGI("[%lld] device unplugged: shutting down in %lld (@ %lld)\n", + now, UNPLUGGED_SHUTDOWN_TIME, charger->next_pwr_check); + } else if (now >= charger->next_pwr_check) { + LOGI("[%lld] shutting down\n", now); + android_reboot(ANDROID_RB_POWEROFF, 0, 0); + } else { + /* otherwise we already have a shutdown timer scheduled */ + } + } else { + /* online supply present, reset shutdown timer if set */ + if (charger->next_pwr_check != -1) { + LOGI("[%lld] device plugged in: shutdown cancelled\n", now); + kick_animation(charger->batt_anim); + } + charger->next_pwr_check = -1; + } +} + +static void wait_next_event(struct charger *charger, int64_t now) +{ + int64_t next_event = INT64_MAX; + int64_t timeout; + struct input_event ev; + int ret; + + LOGV("[%lld] next screen: %lld next key: %lld next pwr: %lld\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; + LOGV("[%lld] blocking (%lld)\n", now, timeout); + ret = ev_wait((int)timeout); + if (!ret) + ev_dispatch(); +} + +static int input_callback(int fd, short revents, void *data) +{ + struct charger *charger = data; + struct input_event ev; + int ret; + + ret = ev_get_input(fd, revents, &ev); + if (ret) + return -1; + update_input_state(charger, &ev); + return 0; +} + +static void event_loop(struct charger *charger) +{ + int ret; + + while (true) { + int64_t now = curr_time_ms(); + + LOGV("[%lld] event_loop()\n", now); + 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); + + wait_next_event(charger, now); + } +} + +static int alarm_open_alm_dev() +{ + int fd; + + fd = open("/dev/alarm", O_RDWR); + if(fd < 0 ) + LOGE("Can't open alarm devfs node\n"); + + return fd; +} + +static void alarm_close_alm_dev(int fd) +{ + close(fd); +} + +static int alarm_open_rtc_dev() +{ + int fd; + + fd = open("/dev/rtc0", O_RDWR); + if (fd < 0 ) + LOGE("Can't open rtc devfs node\n"); + + return fd; +} + +static void alarm_close_rtc_dev(int fd) +{ + close(fd); +} + +static int alarm_set_reboot_time(int fd, int type, time_t secs) +{ + struct timespec ts; + ts.tv_sec = secs; + ts.tv_nsec = 0; + int ret; + + ret = ioctl(fd, ANDROID_ALARM_SET(type), &ts); + if (ret < 0) + LOGE("Unable to set reboot time to %d\n", secs); + return ret; +} + +static int alarm_get_alm_time(int fd, time_t *secs) +{ + struct tm alm_tm; + int ret; + + ret = ioctl(fd, RTC_ALM_READ, &alm_tm); + if (ret < 0) { + LOGE("Unable to get alarm time\n"); + goto err; + } + + *secs = mktime(&alm_tm) + alm_tm.tm_gmtoff; + if (*secs < 0) { + LOGE("Invalid alarm seconds = %ld\n", *secs); + goto err; + } + + return 0; + +err: + return -1; +} + +static int alarm_get_rtc_time(int fd, time_t *secs) +{ + struct tm rtc_tm; + int ret; + + ret = ioctl(fd, RTC_RD_TIME, &rtc_tm); + if (ret < 0) { + LOGE("Unable to get rtc time\n"); + goto err; + } + + *secs = mktime(&rtc_tm) + rtc_tm.tm_gmtoff; + if (*secs < 0) { + LOGE("Invalid rtc seconds = %ld\n", *secs); + goto err; + } + + return 0; + +err: + return -1; +} + +static int alarm_wait(int fd) +{ + int ret = 0; + + do { + ret = ioctl(fd, ANDROID_ALARM_WAIT); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + LOGE("Unable to wait on alarm\n"); + return 0; + } + + return ret; +} + +static void alarm_reboot() +{ + android_reboot(ANDROID_RB_RESTART, 0, 0); +} + +void *alarm_thread(void *p) +{ + int alm_fd, rtc_fd, ret; + time_t g_alm_secs, g_rtc_secs, s_rb_secs; + + rtc_fd = alarm_open_rtc_dev(); + if (rtc_fd < 0) + goto rtc_err; + + ret = alarm_get_alm_time(rtc_fd, &g_alm_secs); + if (ret < 0 || !g_alm_secs) + goto rtc_err; + + ret = alarm_get_rtc_time(rtc_fd, &g_rtc_secs); + if (ret < 0) + goto rtc_err; + + s_rb_secs = g_alm_secs - g_rtc_secs; + if (s_rb_secs <= 0) + goto rtc_err; + + alm_fd = alarm_open_alm_dev(); + if (alm_fd < 0) + goto rtc_err; + + ret = alarm_set_reboot_time(alm_fd, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + s_rb_secs); + if (ret < 0) + goto alm_err; + + ret = alarm_wait(alm_fd); + if (ret) { + LOGI("Exit from power off charging, reboot the phone!\n"); + alarm_reboot(); + } + +alm_err: + alarm_close_alm_dev(alm_fd); + +rtc_err: + alarm_close_rtc_dev(rtc_fd); + + LOGE("Exit from alarm thread\n"); + return NULL; +} + +void alarm_thread_create() +{ + pthread_t tid; + int ret; + + ret = pthread_create(&tid, NULL, alarm_thread, NULL); + if (ret < 0) + LOGE("Create alarm thread failed\n"); +} + +int main(int argc, char **argv) +{ + int ret; + struct charger *charger = &charger_state; + int64_t now = curr_time_ms() - 1; + int fd; + int i; + + list_init(&charger->supplies); + + klog_init(); + klog_set_level(CHARGER_KLOG_LEVEL); + + dump_last_kmsg(); + + alarm_thread_create(); + + LOGI("--------------- STARTING CHARGER MODE ---------------\n"); + + gr_init(); + gr_font_size(&char_width, &char_height); + + ev_init(input_callback, charger); + + fd = uevent_open_socket(64*1024, true); + if (fd >= 0) { + fcntl(fd, F_SETFL, O_NONBLOCK); + ev_add_fd(fd, uevent_callback, charger); + } + charger->uevent_fd = fd; + coldboot(charger, "/sys/class/power_supply", "add"); + + ret = res_create_display_surface("charger/battery_fail_0", &charger->surf_unknown); + if (ret < 0) { + LOGE("Cannot load image\n"); + charger->surf_unknown = NULL; + } + + for (i = 0; i < charger->batt_anim->num_frames; i++) { + struct frame *frame = &charger->batt_anim->frames[i]; + + ret = res_create_display_surface(frame->name, &frame->surface); + if (ret < 0) { + LOGE("Cannot load image %s\n", frame->name); + /* TODO: free the already allocated surfaces... */ + charger->batt_anim->num_frames = 0; + charger->batt_anim->num_cycles = 1; + break; + } + } + + ev_sync_key_state(set_key_callback, charger); + + set_backlight(false); + gr_fb_blank(true); + + charger->next_screen_transition = now - 1; + charger->next_key_check = -1; + charger->next_pwr_check = -1; + reset_animation(charger->batt_anim); + kick_animation(charger->batt_anim); + + event_loop(charger); + + return 0; +} diff --git a/charger/images/battery_0.png b/charger/images/battery_0.png new file mode 100644 index 0000000..1cf0ff2 Binary files /dev/null and b/charger/images/battery_0.png differ diff --git a/charger/images/battery_1.png b/charger/images/battery_1.png new file mode 100644 index 0000000..0084b40 Binary files /dev/null and b/charger/images/battery_1.png differ diff --git a/charger/images/battery_2.png b/charger/images/battery_2.png new file mode 100644 index 0000000..82e789c Binary files /dev/null and b/charger/images/battery_2.png differ diff --git a/charger/images/battery_3.png b/charger/images/battery_3.png new file mode 100644 index 0000000..4d9522e Binary files /dev/null and b/charger/images/battery_3.png differ diff --git a/charger/images/battery_4.png b/charger/images/battery_4.png new file mode 100644 index 0000000..d78868e Binary files /dev/null and b/charger/images/battery_4.png differ diff --git a/charger/images/battery_5.png b/charger/images/battery_5.png new file mode 100644 index 0000000..9635227 Binary files /dev/null and b/charger/images/battery_5.png differ diff --git a/charger/images/battery_charge.png b/charger/images/battery_charge.png new file mode 100644 index 0000000..4c9b4e8 Binary files /dev/null and b/charger/images/battery_charge.png differ diff --git a/charger/images/battery_fail_0.png b/charger/images/battery_fail_0.png new file mode 100755 index 0000000..6e92210 Binary files /dev/null and b/charger/images/battery_fail_0.png differ diff --git a/device_w7.mk b/device_w7.mk index f1cd136..520ae3f 100644 --- a/device_w7.mk +++ b/device_w7.mk @@ -127,6 +127,11 @@ PRODUCT_COPY_FILES += \ PRODUCT_COPY_FILES += \ device/lge/w7/rootdir/twrp.fstab:recovery/root/etc/twrp.fstab +# Offmode Charging +PRODUCT_PACKAGES += \ + charger_res_w7 \ + charger_w7 + # Audio PRODUCT_PACKAGES += \ audio.primary.msm8226 \ diff --git a/rootdir/init.w7.rc b/rootdir/init.w7.rc index 9c23a12..9127300 100755 --- a/rootdir/init.w7.rc +++ b/rootdir/init.w7.rc @@ -106,6 +106,13 @@ service audiod /system/bin/audiod user system group system +service charger /sbin/charger_w7 + class charger + critical + +on charger + class_start charger + on boot chown system system /sys/class/power_supply/battery/pseudo_batt