1 /*
2 * Copyright (C) 2011-2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <dirent.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <inttypes.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/epoll.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <sys/un.h>
28 #include <time.h>
29 #include <unistd.h>
30
31 #include <functional>
32
33 #include <android-base/file.h>
34 #include <android-base/macros.h>
35
36 #include <linux/netlink.h>
37 #include <sys/socket.h>
38
39 #include <cutils/android_get_control_file.h>
40 #include <cutils/klog.h>
41 #include <cutils/misc.h>
42 #include <cutils/properties.h>
43 #include <cutils/uevent.h>
44 #include <sys/reboot.h>
45
46 #ifdef CHARGER_ENABLE_SUSPEND
47 #include <suspend/autosuspend.h>
48 #endif
49
50 #include "AnimationParser.h"
51 #include "healthd_draw.h"
52
53 #include <health2/Health.h>
54 #include <healthd/healthd.h>
55
56 using namespace android;
57
58 // main healthd loop
59 extern int healthd_main(void);
60
61 char* locale;
62
63 #ifndef max
64 #define max(a, b) ((a) > (b) ? (a) : (b))
65 #endif
66
67 #ifndef min
68 #define min(a, b) ((a) < (b) ? (a) : (b))
69 #endif
70
71 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
72
73 #define MSEC_PER_SEC (1000LL)
74 #define NSEC_PER_MSEC (1000000LL)
75
76 #define BATTERY_UNKNOWN_TIME (2 * MSEC_PER_SEC)
77 #define POWER_ON_KEY_TIME (2 * MSEC_PER_SEC)
78 #define UNPLUGGED_SHUTDOWN_TIME (10 * MSEC_PER_SEC)
79 #define UNPLUGGED_DISPLAY_TIME (3 * MSEC_PER_SEC)
80 #define MAX_BATT_LEVEL_WAIT_TIME (3 * MSEC_PER_SEC)
81
82 #define LAST_KMSG_MAX_SZ (32 * 1024)
83
84 #define LOGE(x...) KLOG_ERROR("charger", x);
85 #define LOGW(x...) KLOG_WARNING("charger", x);
86 #define LOGV(x...) KLOG_DEBUG("charger", x);
87
88 // Resources in /product/etc/res overrides resources in /res.
89 // If the device is using the Generic System Image (GSI), resources may exist in
90 // both paths.
91 static constexpr const char* product_animation_desc_path =
92 "/product/etc/res/values/charger/animation.txt";
93 static constexpr const char* product_animation_root = "/product/etc/res/images/";
94 static constexpr const char* animation_desc_path = "/res/values/charger/animation.txt";
95
96 struct key_state {
97 bool pending;
98 bool down;
99 int64_t timestamp;
100 };
101
102 struct charger {
103 bool have_battery_state;
104 bool charger_connected;
105 bool screen_blanked;
106 int64_t next_screen_transition;
107 int64_t next_key_check;
108 int64_t next_pwr_check;
109 int64_t wait_batt_level_timestamp;
110
111 key_state keys[KEY_MAX + 1];
112
113 animation* batt_anim;
114 GRSurface* surf_unknown;
115 int boot_min_cap;
116 };
117
118 static const animation BASE_ANIMATION = {
119 .text_clock =
120 {
121 .pos_x = 0,
122 .pos_y = 0,
123
124 .color_r = 255,
125 .color_g = 255,
126 .color_b = 255,
127 .color_a = 255,
128
129 .font = nullptr,
130 },
131 .text_percent =
132 {
133 .pos_x = 0,
134 .pos_y = 0,
135
136 .color_r = 255,
137 .color_g = 255,
138 .color_b = 255,
139 .color_a = 255,
140 },
141
142 .run = false,
143
144 .frames = nullptr,
145 .cur_frame = 0,
146 .num_frames = 0,
147 .first_frame_repeats = 2,
148
149 .cur_cycle = 0,
150 .num_cycles = 3,
151
152 .cur_level = 0,
153 .cur_status = BATTERY_STATUS_UNKNOWN,
154 };
155
156 static animation::frame default_animation_frames[] = {
157 {
158 .disp_time = 750,
159 .min_level = 0,
160 .max_level = 19,
161 .surface = NULL,
162 },
163 {
164 .disp_time = 750,
165 .min_level = 0,
166 .max_level = 39,
167 .surface = NULL,
168 },
169 {
170 .disp_time = 750,
171 .min_level = 0,
172 .max_level = 59,
173 .surface = NULL,
174 },
175 {
176 .disp_time = 750,
177 .min_level = 0,
178 .max_level = 79,
179 .surface = NULL,
180 },
181 {
182 .disp_time = 750,
183 .min_level = 80,
184 .max_level = 95,
185 .surface = NULL,
186 },
187 {
188 .disp_time = 750,
189 .min_level = 0,
190 .max_level = 100,
191 .surface = NULL,
192 },
193 };
194
195 static animation battery_animation = BASE_ANIMATION;
196
197 static charger charger_state;
198 static healthd_config* healthd_config;
199 static android::BatteryProperties* batt_prop;
200 static std::unique_ptr<HealthdDraw> healthd_draw;
201
202 /* current time in milliseconds */
curr_time_ms()203 static int64_t curr_time_ms() {
204 timespec tm;
205 clock_gettime(CLOCK_MONOTONIC, &tm);
206 return tm.tv_sec * MSEC_PER_SEC + (tm.tv_nsec / NSEC_PER_MSEC);
207 }
208
209 #define MAX_KLOG_WRITE_BUF_SZ 256
210
dump_last_kmsg(void)211 static void dump_last_kmsg(void) {
212 std::string buf;
213 char* ptr;
214 size_t len;
215
216 LOGW("\n");
217 LOGW("*************** LAST KMSG ***************\n");
218 LOGW("\n");
219 const char* kmsg[] = {
220 // clang-format off
221 "/sys/fs/pstore/console-ramoops-0",
222 "/sys/fs/pstore/console-ramoops",
223 "/proc/last_kmsg",
224 // clang-format on
225 };
226 for (size_t i = 0; i < arraysize(kmsg) && buf.empty(); ++i) {
227 auto fd = android_get_control_file(kmsg[i]);
228 if (fd >= 0) {
229 android::base::ReadFdToString(fd, &buf);
230 } else {
231 android::base::ReadFileToString(kmsg[i], &buf);
232 }
233 }
234
235 if (buf.empty()) {
236 LOGW("last_kmsg not found. Cold reset?\n");
237 goto out;
238 }
239
240 len = min(buf.size(), LAST_KMSG_MAX_SZ);
241 ptr = &buf[buf.size() - len];
242
243 while (len > 0) {
244 size_t cnt = min(len, MAX_KLOG_WRITE_BUF_SZ);
245 char yoink;
246 char* nl;
247
248 nl = (char*)memrchr(ptr, '\n', cnt - 1);
249 if (nl) cnt = nl - ptr + 1;
250
251 yoink = ptr[cnt];
252 ptr[cnt] = '\0';
253 klog_write(6, "<4>%s", ptr);
254 ptr[cnt] = yoink;
255
256 len -= cnt;
257 ptr += cnt;
258 }
259
260 out:
261 LOGW("\n");
262 LOGW("************* END LAST KMSG *************\n");
263 LOGW("\n");
264 }
265
266 #ifdef CHARGER_ENABLE_SUSPEND
request_suspend(bool enable)267 static int request_suspend(bool enable) {
268 if (enable)
269 return autosuspend_enable();
270 else
271 return autosuspend_disable();
272 }
273 #else
request_suspend(bool)274 static int request_suspend(bool /*enable*/) {
275 return 0;
276 }
277 #endif
278
kick_animation(animation * anim)279 static void kick_animation(animation* anim) {
280 anim->run = true;
281 }
282
reset_animation(animation * anim)283 static void reset_animation(animation* anim) {
284 anim->cur_cycle = 0;
285 anim->cur_frame = 0;
286 anim->run = false;
287 }
288
update_screen_state(charger * charger,int64_t now)289 static void update_screen_state(charger* charger, int64_t now) {
290 animation* batt_anim = charger->batt_anim;
291 int disp_time;
292
293 if (!batt_anim->run || now < charger->next_screen_transition) return;
294
295 // If battery level is not ready, keep checking in the defined time
296 if (batt_prop == nullptr ||
297 (batt_prop->batteryLevel == 0 && batt_prop->batteryStatus == BATTERY_STATUS_UNKNOWN)) {
298 if (charger->wait_batt_level_timestamp == 0) {
299 // Set max delay time and skip drawing screen
300 charger->wait_batt_level_timestamp = now + MAX_BATT_LEVEL_WAIT_TIME;
301 LOGV("[%" PRId64 "] wait for battery capacity ready\n", now);
302 return;
303 } else if (now <= charger->wait_batt_level_timestamp) {
304 // Do nothing, keep waiting
305 return;
306 }
307 // If timeout and battery level is still not ready, draw unknown battery
308 }
309
310 if (healthd_draw == nullptr) {
311 if (healthd_config && healthd_config->screen_on) {
312 if (!healthd_config->screen_on(batt_prop)) {
313 LOGV("[%" PRId64 "] leave screen off\n", now);
314 batt_anim->run = false;
315 charger->next_screen_transition = -1;
316 if (charger->charger_connected) request_suspend(true);
317 return;
318 }
319 }
320
321 healthd_draw.reset(new HealthdDraw(batt_anim));
322
323 #ifndef CHARGER_DISABLE_INIT_BLANK
324 healthd_draw->blank_screen(true);
325 charger->screen_blanked = true;
326 #endif
327 }
328
329 /* animation is over, blank screen and leave */
330 if (batt_anim->num_cycles > 0 && batt_anim->cur_cycle == batt_anim->num_cycles) {
331 reset_animation(batt_anim);
332 charger->next_screen_transition = -1;
333 healthd_draw->blank_screen(true);
334 charger->screen_blanked = true;
335 LOGV("[%" PRId64 "] animation done\n", now);
336 if (charger->charger_connected) request_suspend(true);
337 return;
338 }
339
340 disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time;
341
342 if (charger->screen_blanked) {
343 healthd_draw->blank_screen(false);
344 charger->screen_blanked = false;
345 }
346
347 /* animation starting, set up the animation */
348 if (batt_anim->cur_frame == 0) {
349 LOGV("[%" PRId64 "] animation starting\n", now);
350 if (batt_prop) {
351 batt_anim->cur_level = batt_prop->batteryLevel;
352 batt_anim->cur_status = batt_prop->batteryStatus;
353 if (batt_prop->batteryLevel >= 0 && batt_anim->num_frames != 0) {
354 /* find first frame given current battery level */
355 for (int i = 0; i < batt_anim->num_frames; i++) {
356 if (batt_anim->cur_level >= batt_anim->frames[i].min_level &&
357 batt_anim->cur_level <= batt_anim->frames[i].max_level) {
358 batt_anim->cur_frame = i;
359 break;
360 }
361 }
362
363 if (charger->charger_connected) {
364 // repeat the first frame first_frame_repeats times
365 disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time *
366 batt_anim->first_frame_repeats;
367 } else {
368 disp_time = UNPLUGGED_DISPLAY_TIME / batt_anim->num_cycles;
369 }
370
371 LOGV("cur_frame=%d disp_time=%d\n", batt_anim->cur_frame, disp_time);
372 }
373 }
374 }
375
376 /* draw the new frame (@ cur_frame) */
377 healthd_draw->redraw_screen(charger->batt_anim, charger->surf_unknown);
378
379 /* if we don't have anim frames, we only have one image, so just bump
380 * the cycle counter and exit
381 */
382 if (batt_anim->num_frames == 0 || batt_anim->cur_level < 0) {
383 LOGW("[%" PRId64 "] animation missing or unknown battery status\n", now);
384 charger->next_screen_transition = now + BATTERY_UNKNOWN_TIME;
385 batt_anim->cur_cycle++;
386 return;
387 }
388
389 /* schedule next screen transition */
390 charger->next_screen_transition = curr_time_ms() + disp_time;
391
392 /* advance frame cntr to the next valid frame only if we are charging
393 * if necessary, advance cycle cntr, and reset frame cntr
394 */
395 if (charger->charger_connected) {
396 batt_anim->cur_frame++;
397
398 while (batt_anim->cur_frame < batt_anim->num_frames &&
399 (batt_anim->cur_level < batt_anim->frames[batt_anim->cur_frame].min_level ||
400 batt_anim->cur_level > batt_anim->frames[batt_anim->cur_frame].max_level)) {
401 batt_anim->cur_frame++;
402 }
403 if (batt_anim->cur_frame >= batt_anim->num_frames) {
404 batt_anim->cur_cycle++;
405 batt_anim->cur_frame = 0;
406
407 /* don't reset the cycle counter, since we use that as a signal
408 * in a test above to check if animation is over
409 */
410 }
411 } else {
412 /* Stop animating if we're not charging.
413 * If we stop it immediately instead of going through this loop, then
414 * the animation would stop somewhere in the middle.
415 */
416 batt_anim->cur_frame = 0;
417 batt_anim->cur_cycle++;
418 }
419 }
420
set_key_callback(charger * charger,int code,int value)421 static int set_key_callback(charger* charger, int code, int value) {
422 int64_t now = curr_time_ms();
423 int down = !!value;
424
425 if (code > KEY_MAX) return -1;
426
427 /* ignore events that don't modify our state */
428 if (charger->keys[code].down == down) return 0;
429
430 /* only record the down even timestamp, as the amount
431 * of time the key spent not being pressed is not useful */
432 if (down) charger->keys[code].timestamp = now;
433 charger->keys[code].down = down;
434 charger->keys[code].pending = true;
435 if (down) {
436 LOGV("[%" PRId64 "] key[%d] down\n", now, code);
437 } else {
438 int64_t duration = now - charger->keys[code].timestamp;
439 int64_t secs = duration / 1000;
440 int64_t msecs = duration - secs * 1000;
441 LOGV("[%" PRId64 "] key[%d] up (was down for %" PRId64 ".%" PRId64 "sec)\n", now, code,
442 secs, msecs);
443 }
444
445 return 0;
446 }
447
update_input_state(charger * charger,input_event * ev)448 static void update_input_state(charger* charger, input_event* ev) {
449 if (ev->type != EV_KEY) return;
450 set_key_callback(charger, ev->code, ev->value);
451 }
452
set_next_key_check(charger * charger,key_state * key,int64_t timeout)453 static void set_next_key_check(charger* charger, key_state* key, int64_t timeout) {
454 int64_t then = key->timestamp + timeout;
455
456 if (charger->next_key_check == -1 || then < charger->next_key_check)
457 charger->next_key_check = then;
458 }
459
process_key(charger * charger,int code,int64_t now)460 static void process_key(charger* charger, int code, int64_t now) {
461 key_state* key = &charger->keys[code];
462
463 if (code == KEY_POWER) {
464 if (key->down) {
465 int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME;
466 if (now >= reboot_timeout) {
467 /* We do not currently support booting from charger mode on
468 all devices. Check the property and continue booting or reboot
469 accordingly. */
470 if (property_get_bool("ro.enable_boot_charger_mode", false)) {
471 LOGW("[%" PRId64 "] booting from charger mode\n", now);
472 property_set("sys.boot_from_charger_mode", "1");
473 } else {
474 if (charger->batt_anim->cur_level >= charger->boot_min_cap) {
475 LOGW("[%" PRId64 "] rebooting\n", now);
476 reboot(RB_AUTOBOOT);
477 } else {
478 LOGV("[%" PRId64
479 "] ignore power-button press, battery level "
480 "less than minimum\n",
481 now);
482 }
483 }
484 } else {
485 /* if the key is pressed but timeout hasn't expired,
486 * make sure we wake up at the right-ish time to check
487 */
488 set_next_key_check(charger, key, POWER_ON_KEY_TIME);
489
490 /* Turn on the display and kick animation on power-key press
491 * rather than on key release
492 */
493 kick_animation(charger->batt_anim);
494 request_suspend(false);
495 }
496 } else {
497 /* if the power key got released, force screen state cycle */
498 if (key->pending) {
499 kick_animation(charger->batt_anim);
500 request_suspend(false);
501 }
502 }
503 }
504
505 key->pending = false;
506 }
507
handle_input_state(charger * charger,int64_t now)508 static void handle_input_state(charger* charger, int64_t now) {
509 process_key(charger, KEY_POWER, now);
510
511 if (charger->next_key_check != -1 && now > charger->next_key_check)
512 charger->next_key_check = -1;
513 }
514
handle_power_supply_state(charger * charger,int64_t now)515 static void handle_power_supply_state(charger* charger, int64_t now) {
516 if (!charger->have_battery_state) return;
517
518 if (!charger->charger_connected) {
519 request_suspend(false);
520 if (charger->next_pwr_check == -1) {
521 /* Last cycle would have stopped at the extreme top of battery-icon
522 * Need to show the correct level corresponding to capacity.
523 *
524 * Reset next_screen_transition to update screen immediately.
525 * Reset & kick animation to show complete animation cycles
526 * when charger disconnected.
527 */
528 charger->next_screen_transition = now - 1;
529 reset_animation(charger->batt_anim);
530 kick_animation(charger->batt_anim);
531 charger->next_pwr_check = now + UNPLUGGED_SHUTDOWN_TIME;
532 LOGW("[%" PRId64 "] device unplugged: shutting down in %" PRId64 " (@ %" PRId64 ")\n",
533 now, (int64_t)UNPLUGGED_SHUTDOWN_TIME, charger->next_pwr_check);
534 } else if (now >= charger->next_pwr_check) {
535 LOGW("[%" PRId64 "] shutting down\n", now);
536 reboot(RB_POWER_OFF);
537 } else {
538 /* otherwise we already have a shutdown timer scheduled */
539 }
540 } else {
541 /* online supply present, reset shutdown timer if set */
542 if (charger->next_pwr_check != -1) {
543 /* Reset next_screen_transition to update screen immediately.
544 * Reset & kick animation to show complete animation cycles
545 * when charger connected again.
546 */
547 request_suspend(false);
548 charger->next_screen_transition = now - 1;
549 reset_animation(charger->batt_anim);
550 kick_animation(charger->batt_anim);
551 LOGW("[%" PRId64 "] device plugged in: shutdown cancelled\n", now);
552 }
553 charger->next_pwr_check = -1;
554 }
555 }
556
healthd_mode_charger_heartbeat()557 void healthd_mode_charger_heartbeat() {
558 charger* charger = &charger_state;
559 int64_t now = curr_time_ms();
560
561 handle_input_state(charger, now);
562 handle_power_supply_state(charger, now);
563
564 /* do screen update last in case any of the above want to start
565 * screen transitions (animations, etc)
566 */
567 update_screen_state(charger, now);
568 }
569
healthd_mode_charger_battery_update(android::BatteryProperties * props)570 void healthd_mode_charger_battery_update(android::BatteryProperties* props) {
571 charger* charger = &charger_state;
572
573 charger->charger_connected =
574 props->chargerAcOnline || props->chargerUsbOnline || props->chargerWirelessOnline;
575
576 if (!charger->have_battery_state) {
577 charger->have_battery_state = true;
578 charger->next_screen_transition = curr_time_ms() - 1;
579 request_suspend(false);
580 reset_animation(charger->batt_anim);
581 kick_animation(charger->batt_anim);
582 }
583 batt_prop = props;
584 }
585
healthd_mode_charger_preparetowait(void)586 int healthd_mode_charger_preparetowait(void) {
587 charger* charger = &charger_state;
588 int64_t now = curr_time_ms();
589 int64_t next_event = INT64_MAX;
590 int64_t timeout;
591
592 LOGV("[%" PRId64 "] next screen: %" PRId64 " next key: %" PRId64 " next pwr: %" PRId64 "\n",
593 now, charger->next_screen_transition, charger->next_key_check, charger->next_pwr_check);
594
595 if (charger->next_screen_transition != -1) next_event = charger->next_screen_transition;
596 if (charger->next_key_check != -1 && charger->next_key_check < next_event)
597 next_event = charger->next_key_check;
598 if (charger->next_pwr_check != -1 && charger->next_pwr_check < next_event)
599 next_event = charger->next_pwr_check;
600
601 if (next_event != -1 && next_event != INT64_MAX)
602 timeout = max(0, next_event - now);
603 else
604 timeout = -1;
605
606 return (int)timeout;
607 }
608
input_callback(charger * charger,int fd,unsigned int epevents)609 static int input_callback(charger* charger, int fd, unsigned int epevents) {
610 input_event ev;
611 int ret;
612
613 ret = ev_get_input(fd, epevents, &ev);
614 if (ret) return -1;
615 update_input_state(charger, &ev);
616 return 0;
617 }
618
charger_event_handler(uint32_t)619 static void charger_event_handler(uint32_t /*epevents*/) {
620 int ret;
621
622 ret = ev_wait(-1);
623 if (!ret) ev_dispatch();
624 }
625
init_animation()626 animation* init_animation() {
627 bool parse_success;
628
629 std::string content;
630 if (base::ReadFileToString(product_animation_desc_path, &content)) {
631 parse_success = parse_animation_desc(content, &battery_animation);
632 battery_animation.set_resource_root(product_animation_root);
633 } else if (base::ReadFileToString(animation_desc_path, &content)) {
634 parse_success = parse_animation_desc(content, &battery_animation);
635 } else {
636 LOGW("Could not open animation description at %s\n", animation_desc_path);
637 parse_success = false;
638 }
639
640 if (!parse_success) {
641 LOGW("Could not parse animation description. Using default animation.\n");
642 battery_animation = BASE_ANIMATION;
643 battery_animation.animation_file.assign("charger/battery_scale");
644 battery_animation.frames = default_animation_frames;
645 battery_animation.num_frames = ARRAY_SIZE(default_animation_frames);
646 }
647 if (battery_animation.fail_file.empty()) {
648 battery_animation.fail_file.assign("charger/battery_fail");
649 }
650
651 LOGV("Animation Description:\n");
652 LOGV(" animation: %d %d '%s' (%d)\n", battery_animation.num_cycles,
653 battery_animation.first_frame_repeats, battery_animation.animation_file.c_str(),
654 battery_animation.num_frames);
655 LOGV(" fail_file: '%s'\n", battery_animation.fail_file.c_str());
656 LOGV(" clock: %d %d %d %d %d %d '%s'\n", battery_animation.text_clock.pos_x,
657 battery_animation.text_clock.pos_y, battery_animation.text_clock.color_r,
658 battery_animation.text_clock.color_g, battery_animation.text_clock.color_b,
659 battery_animation.text_clock.color_a, battery_animation.text_clock.font_file.c_str());
660 LOGV(" percent: %d %d %d %d %d %d '%s'\n", battery_animation.text_percent.pos_x,
661 battery_animation.text_percent.pos_y, battery_animation.text_percent.color_r,
662 battery_animation.text_percent.color_g, battery_animation.text_percent.color_b,
663 battery_animation.text_percent.color_a, battery_animation.text_percent.font_file.c_str());
664 for (int i = 0; i < battery_animation.num_frames; i++) {
665 LOGV(" frame %.2d: %d %d %d\n", i, battery_animation.frames[i].disp_time,
666 battery_animation.frames[i].min_level, battery_animation.frames[i].max_level);
667 }
668
669 return &battery_animation;
670 }
671
healthd_mode_charger_init(struct healthd_config * config)672 void healthd_mode_charger_init(struct healthd_config* config) {
673 using android::hardware::health::V2_0::implementation::Health;
674
675 int ret;
676 charger* charger = &charger_state;
677 int i;
678 int epollfd;
679
680 dump_last_kmsg();
681
682 LOGW("--------------- STARTING CHARGER MODE ---------------\n");
683
684 ret = ev_init(std::bind(&input_callback, charger, std::placeholders::_1, std::placeholders::_2));
685 if (!ret) {
686 epollfd = ev_get_epollfd();
687 healthd_register_event(epollfd, charger_event_handler, EVENT_WAKEUP_FD);
688 }
689
690 animation* anim = init_animation();
691 charger->batt_anim = anim;
692
693 ret = res_create_display_surface(anim->fail_file.c_str(), &charger->surf_unknown);
694 if (ret < 0) {
695 LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret);
696 ret = res_create_display_surface("charger/battery_fail", &charger->surf_unknown);
697 if (ret < 0) {
698 LOGE("Cannot load built in battery_fail image\n");
699 charger->surf_unknown = NULL;
700 }
701 }
702
703 GRSurface** scale_frames;
704 int scale_count;
705 int scale_fps; // Not in use (charger/battery_scale doesn't have FPS text
706 // chunk). We are using hard-coded frame.disp_time instead.
707 ret = res_create_multi_display_surface(anim->animation_file.c_str(), &scale_count, &scale_fps,
708 &scale_frames);
709 if (ret < 0) {
710 LOGE("Cannot load battery_scale image\n");
711 anim->num_frames = 0;
712 anim->num_cycles = 1;
713 } else if (scale_count != anim->num_frames) {
714 LOGE("battery_scale image has unexpected frame count (%d, expected %d)\n", scale_count,
715 anim->num_frames);
716 anim->num_frames = 0;
717 anim->num_cycles = 1;
718 } else {
719 for (i = 0; i < anim->num_frames; i++) {
720 anim->frames[i].surface = scale_frames[i];
721 }
722 }
723 ev_sync_key_state(
724 std::bind(&set_key_callback, charger, std::placeholders::_1, std::placeholders::_2));
725
726 charger->next_screen_transition = -1;
727 charger->next_key_check = -1;
728 charger->next_pwr_check = -1;
729 charger->wait_batt_level_timestamp = 0;
730
731 // Initialize Health implementation (which initializes the internal BatteryMonitor).
732 Health::initInstance(config);
733
734 healthd_config = config;
735 charger->boot_min_cap = config->boot_min_cap;
736 }
737
738 static struct healthd_mode_ops charger_ops = {
739 .init = healthd_mode_charger_init,
740 .preparetowait = healthd_mode_charger_preparetowait,
741 .heartbeat = healthd_mode_charger_heartbeat,
742 .battery_update = healthd_mode_charger_battery_update,
743 };
744
healthd_charger_main(int argc,char ** argv)745 int healthd_charger_main(int argc, char** argv) {
746 int ch;
747
748 healthd_mode_ops = &charger_ops;
749
750 while ((ch = getopt(argc, argv, "cr")) != -1) {
751 switch (ch) {
752 case 'c':
753 // -c is now a noop
754 break;
755 case 'r':
756 // -r is now a noop
757 break;
758 case '?':
759 default:
760 LOGE("Unrecognized charger option: %c\n", optopt);
761 exit(1);
762 }
763 }
764
765 return healthd_main();
766 }
767