| /* |
| * Copyright (C) 2011-2017 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. |
| */ |
| |
| #include "healthd_mode_charger.h" |
| |
| #include <array> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/epoll.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/un.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <optional> |
| |
| #include <android-base/file.h> |
| #include <android-base/macros.h> |
| |
| #include <linux/netlink.h> |
| #include <sys/socket.h> |
| |
| #include <cutils/android_get_control_file.h> |
| #include <cutils/klog.h> |
| #include <cutils/misc.h> |
| #include <cutils/properties.h> |
| #include <cutils/uevent.h> |
| #include <sys/reboot.h> |
| |
| #include <suspend/autosuspend.h> |
| |
| #include "AnimationParser.h" |
| #include "charger.sysprop.h" |
| #include "charger_utils.h" |
| #include "healthd_draw.h" |
| |
| #include <android/hardware/health/2.0/IHealthInfoCallback.h> |
| #include <health/utils.h> |
| #include <health2impl/HalHealthLoop.h> |
| #include <health2impl/Health.h> |
| #include <healthd/healthd.h> |
| |
| using namespace android; |
| using android::hardware::Return; |
| using android::hardware::health::GetHealthServiceOrDefault; |
| using android::hardware::health::HealthLoop; |
| using android::hardware::health::V1_0::BatteryStatus; |
| using android::hardware::health::V2_0::Result; |
| using android::hardware::health::V2_1::IHealth; |
| using IHealth_2_0 = android::hardware::health::V2_0::IHealth; |
| using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo; |
| using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo; |
| |
| // main healthd loop |
| extern int healthd_main(void); |
| |
| // minui globals |
| char* locale; |
| |
| #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 (10 * MSEC_PER_SEC) |
| #define UNPLUGGED_DISPLAY_TIME (3 * MSEC_PER_SEC) |
| #define MAX_BATT_LEVEL_WAIT_TIME (3 * MSEC_PER_SEC) |
| #define UNPLUGGED_SHUTDOWN_TIME_PROP "ro.product.charger.unplugged_shutdown_time" |
| |
| #define LAST_KMSG_MAX_SZ (32 * 1024) |
| |
| #define LOGE(x...) KLOG_ERROR("charger", x); |
| #define LOGW(x...) KLOG_WARNING("charger", x); |
| #define LOGV(x...) KLOG_DEBUG("charger", x); |
| |
| namespace android { |
| |
| // Resources in /product/etc/res overrides resources in /res. |
| // If the device is using the Generic System Image (GSI), resources may exist in |
| // both paths. |
| static constexpr const char* product_animation_desc_path = |
| "/product/etc/res/values/charger/animation.txt"; |
| static constexpr const char* product_animation_root = "/product/etc/res/images/"; |
| static constexpr const char* animation_desc_path = "/res/values/charger/animation.txt"; |
| |
| static const animation BASE_ANIMATION = { |
| .text_clock = |
| { |
| .pos_x = 0, |
| .pos_y = 0, |
| |
| .color_r = 255, |
| .color_g = 255, |
| .color_b = 255, |
| .color_a = 255, |
| |
| .font = nullptr, |
| }, |
| .text_percent = |
| { |
| .pos_x = 0, |
| .pos_y = 0, |
| |
| .color_r = 255, |
| .color_g = 255, |
| .color_b = 255, |
| .color_a = 255, |
| }, |
| |
| .run = false, |
| |
| .frames = nullptr, |
| .cur_frame = 0, |
| .num_frames = 0, |
| .first_frame_repeats = 2, |
| |
| .cur_cycle = 0, |
| .num_cycles = 3, |
| |
| .cur_level = 0, |
| .cur_status = BATTERY_STATUS_UNKNOWN, |
| }; |
| |
| void Charger::InitDefaultAnimationFrames() { |
| owned_frames_ = { |
| { |
| .name = "charger/battery-charging0001", |
| .disp_time = 500, |
| .min_level = 0, |
| .max_level = 1, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0002", |
| .disp_time = 500, |
| .min_level = 2, |
| .max_level = 2, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0003", |
| .disp_time = 500, |
| .min_level = 3, |
| .max_level = 3, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0004", |
| .disp_time = 500, |
| .min_level = 4, |
| .max_level = 4, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0005", |
| .disp_time = 500, |
| .min_level = 5, |
| .max_level = 5, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0006", |
| .disp_time = 500, |
| .min_level = 6, |
| .max_level = 6, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0007", |
| .disp_time = 500, |
| .min_level = 7, |
| .max_level = 7, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0008", |
| .disp_time = 500, |
| .min_level = 8, |
| .max_level = 8, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0009", |
| .disp_time = 500, |
| .min_level = 9, |
| .max_level = 9, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0010", |
| .disp_time = 500, |
| .min_level = 10, |
| .max_level = 10, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0011", |
| .disp_time = 500, |
| .min_level = 11, |
| .max_level = 11, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0012", |
| .disp_time = 500, |
| .min_level = 12, |
| .max_level = 12, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0013", |
| .disp_time = 500, |
| .min_level = 13, |
| .max_level = 13, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0014", |
| .disp_time = 500, |
| .min_level = 14, |
| .max_level = 14, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0015", |
| .disp_time = 500, |
| .min_level = 15, |
| .max_level = 15, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0016", |
| .disp_time = 500, |
| .min_level = 16, |
| .max_level = 16, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0017", |
| .disp_time = 500, |
| .min_level = 17, |
| .max_level = 17, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0018", |
| .disp_time = 500, |
| .min_level = 18, |
| .max_level = 18, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0019", |
| .disp_time = 500, |
| .min_level = 19, |
| .max_level = 19, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0020", |
| .disp_time = 500, |
| .min_level = 20, |
| .max_level = 20, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0021", |
| .disp_time = 500, |
| .min_level = 21, |
| .max_level = 21, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0022", |
| .disp_time = 500, |
| .min_level = 22, |
| .max_level = 22, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0023", |
| .disp_time = 500, |
| .min_level = 23, |
| .max_level = 23, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0024", |
| .disp_time = 500, |
| .min_level = 24, |
| .max_level = 24, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0025", |
| .disp_time = 500, |
| .min_level = 25, |
| .max_level = 25, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0026", |
| .disp_time = 500, |
| .min_level = 26, |
| .max_level = 26, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0027", |
| .disp_time = 500, |
| .min_level = 27, |
| .max_level = 27, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0028", |
| .disp_time = 500, |
| .min_level = 28, |
| .max_level = 28, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0029", |
| .disp_time = 500, |
| .min_level = 29, |
| .max_level = 29, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0030", |
| .disp_time = 500, |
| .min_level = 30, |
| .max_level = 30, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0031", |
| .disp_time = 500, |
| .min_level = 31, |
| .max_level = 31, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0032", |
| .disp_time = 500, |
| .min_level = 32, |
| .max_level = 32, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0033", |
| .disp_time = 500, |
| .min_level = 33, |
| .max_level = 33, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0034", |
| .disp_time = 500, |
| .min_level = 34, |
| .max_level = 34, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0035", |
| .disp_time = 500, |
| .min_level = 35, |
| .max_level = 35, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0036", |
| .disp_time = 500, |
| .min_level = 36, |
| .max_level = 36, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0037", |
| .disp_time = 500, |
| .min_level = 37, |
| .max_level = 37, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0038", |
| .disp_time = 500, |
| .min_level = 38, |
| .max_level = 38, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0039", |
| .disp_time = 500, |
| .min_level = 39, |
| .max_level = 39, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0040", |
| .disp_time = 500, |
| .min_level = 40, |
| .max_level = 40, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0041", |
| .disp_time = 500, |
| .min_level = 41, |
| .max_level = 41, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0042", |
| .disp_time = 500, |
| .min_level = 42, |
| .max_level = 42, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0043", |
| .disp_time = 500, |
| .min_level = 43, |
| .max_level = 43, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0044", |
| .disp_time = 500, |
| .min_level = 44, |
| .max_level = 44, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0045", |
| .disp_time = 500, |
| .min_level = 45, |
| .max_level = 45, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0046", |
| .disp_time = 500, |
| .min_level = 46, |
| .max_level = 46, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0047", |
| .disp_time = 500, |
| .min_level = 47, |
| .max_level = 47, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0048", |
| .disp_time = 500, |
| .min_level = 48, |
| .max_level = 48, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0049", |
| .disp_time = 500, |
| .min_level = 49, |
| .max_level = 49, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0050", |
| .disp_time = 500, |
| .min_level = 50, |
| .max_level = 50, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0051", |
| .disp_time = 500, |
| .min_level = 51, |
| .max_level = 51, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0052", |
| .disp_time = 500, |
| .min_level = 52, |
| .max_level = 52, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0053", |
| .disp_time = 500, |
| .min_level = 53, |
| .max_level = 53, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0054", |
| .disp_time = 500, |
| .min_level = 54, |
| .max_level = 54, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0055", |
| .disp_time = 500, |
| .min_level = 55, |
| .max_level = 55, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0056", |
| .disp_time = 500, |
| .min_level = 56, |
| .max_level = 56, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0057", |
| .disp_time = 500, |
| .min_level = 57, |
| .max_level = 57, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0058", |
| .disp_time = 500, |
| .min_level = 58, |
| .max_level = 58, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0059", |
| .disp_time = 500, |
| .min_level = 59, |
| .max_level = 59, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0060", |
| .disp_time = 500, |
| .min_level = 60, |
| .max_level = 60, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0061", |
| .disp_time = 500, |
| .min_level = 61, |
| .max_level = 61, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0062", |
| .disp_time = 500, |
| .min_level = 62, |
| .max_level = 62, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0063", |
| .disp_time = 500, |
| .min_level = 63, |
| .max_level = 63, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0064", |
| .disp_time = 500, |
| .min_level = 64, |
| .max_level = 64, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0065", |
| .disp_time = 500, |
| .min_level = 65, |
| .max_level = 65, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0066", |
| .disp_time = 500, |
| .min_level = 66, |
| .max_level = 66, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0067", |
| .disp_time = 500, |
| .min_level = 67, |
| .max_level = 67, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0068", |
| .disp_time = 500, |
| .min_level = 68, |
| .max_level = 68, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0069", |
| .disp_time = 500, |
| .min_level = 69, |
| .max_level = 69, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0070", |
| .disp_time = 500, |
| .min_level = 70, |
| .max_level = 70, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0071", |
| .disp_time = 500, |
| .min_level = 71, |
| .max_level = 71, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0072", |
| .disp_time = 500, |
| .min_level = 72, |
| .max_level = 72, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0073", |
| .disp_time = 500, |
| .min_level = 73, |
| .max_level = 73, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0074", |
| .disp_time = 500, |
| .min_level = 74, |
| .max_level = 74, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0075", |
| .disp_time = 500, |
| .min_level = 75, |
| .max_level = 75, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0076", |
| .disp_time = 500, |
| .min_level = 76, |
| .max_level = 76, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0077", |
| .disp_time = 500, |
| .min_level = 77, |
| .max_level = 77, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0078", |
| .disp_time = 500, |
| .min_level = 78, |
| .max_level = 78, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0079", |
| .disp_time = 500, |
| .min_level = 79, |
| .max_level = 79, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0080", |
| .disp_time = 500, |
| .min_level = 80, |
| .max_level = 80, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0081", |
| .disp_time = 500, |
| .min_level = 81, |
| .max_level = 81, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0082", |
| .disp_time = 500, |
| .min_level = 82, |
| .max_level = 82, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0083", |
| .disp_time = 500, |
| .min_level = 83, |
| .max_level = 83, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0084", |
| .disp_time = 500, |
| .min_level = 84, |
| .max_level = 84, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0085", |
| .disp_time = 500, |
| .min_level = 85, |
| .max_level = 85, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0086", |
| .disp_time = 500, |
| .min_level = 86, |
| .max_level = 86, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0087", |
| .disp_time = 500, |
| .min_level = 87, |
| .max_level = 87, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0088", |
| .disp_time = 500, |
| .min_level = 88, |
| .max_level = 88, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0089", |
| .disp_time = 500, |
| .min_level = 89, |
| .max_level = 89, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0090", |
| .disp_time = 500, |
| .min_level = 90, |
| .max_level = 90, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0091", |
| .disp_time = 500, |
| .min_level = 91, |
| .max_level = 91, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0092", |
| .disp_time = 500, |
| .min_level = 92, |
| .max_level = 92, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0093", |
| .disp_time = 500, |
| .min_level = 93, |
| .max_level = 93, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0094", |
| .disp_time = 500, |
| .min_level = 94, |
| .max_level = 94, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0095", |
| .disp_time = 500, |
| .min_level = 95, |
| .max_level = 95, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0096", |
| .disp_time = 500, |
| .min_level = 96, |
| .max_level = 96, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0097", |
| .disp_time = 500, |
| .min_level = 97, |
| .max_level = 97, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0098", |
| .disp_time = 500, |
| .min_level = 98, |
| .max_level = 98, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0099", |
| .disp_time = 500, |
| .min_level = 99, |
| .max_level = 99, |
| .surface = NULL, |
| }, |
| { |
| .name = "charger/battery-charging0100", |
| .disp_time = 500, |
| .min_level = 100, |
| .max_level = 100, |
| .surface = NULL, |
| }, |
| }; |
| } |
| |
| Charger::Charger(const sp<IHealth>& service) |
| : HalHealthLoop("charger", service), batt_anim_(BASE_ANIMATION) {} |
| |
| Charger::~Charger() {} |
| |
| /* current time in milliseconds */ |
| static int64_t curr_time_ms() { |
| timespec tm; |
| clock_gettime(CLOCK_MONOTONIC, &tm); |
| return tm.tv_sec * MSEC_PER_SEC + (tm.tv_nsec / NSEC_PER_MSEC); |
| } |
| |
| #define MAX_KLOG_WRITE_BUF_SZ 256 |
| |
| static void dump_last_kmsg(void) { |
| std::string buf; |
| char* ptr; |
| size_t len; |
| |
| LOGW("\n"); |
| LOGW("*************** LAST KMSG ***************\n"); |
| LOGW("\n"); |
| const char* kmsg[] = { |
| // clang-format off |
| "/sys/fs/pstore/console-ramoops-0", |
| "/sys/fs/pstore/console-ramoops", |
| "/proc/last_kmsg", |
| // clang-format on |
| }; |
| for (size_t i = 0; i < arraysize(kmsg) && buf.empty(); ++i) { |
| auto fd = android_get_control_file(kmsg[i]); |
| if (fd >= 0) { |
| android::base::ReadFdToString(fd, &buf); |
| } else { |
| android::base::ReadFileToString(kmsg[i], &buf); |
| } |
| } |
| |
| if (buf.empty()) { |
| LOGW("last_kmsg not found. Cold reset?\n"); |
| goto out; |
| } |
| |
| len = min(buf.size(), LAST_KMSG_MAX_SZ); |
| ptr = &buf[buf.size() - len]; |
| |
| while (len > 0) { |
| size_t cnt = min(len, MAX_KLOG_WRITE_BUF_SZ); |
| char yoink; |
| char* nl; |
| |
| nl = (char*)memrchr(ptr, '\n', cnt - 1); |
| if (nl) cnt = nl - ptr + 1; |
| |
| yoink = ptr[cnt]; |
| ptr[cnt] = '\0'; |
| klog_write(6, "<4>%s", ptr); |
| ptr[cnt] = yoink; |
| |
| len -= cnt; |
| ptr += cnt; |
| } |
| |
| out: |
| LOGW("\n"); |
| LOGW("************* END LAST KMSG *************\n"); |
| LOGW("\n"); |
| } |
| |
| static int request_suspend(bool enable) { |
| if (!android::sysprop::ChargerProperties::enable_suspend().value_or(false)) { |
| return 0; |
| } |
| |
| if (enable) |
| return autosuspend_enable(); |
| else |
| return autosuspend_disable(); |
| } |
| |
| static void kick_animation(animation* anim) { |
| anim->run = true; |
| } |
| |
| static void reset_animation(animation* anim) { |
| anim->cur_cycle = 0; |
| anim->cur_frame = 0; |
| anim->run = false; |
| } |
| |
| void Charger::UpdateScreenState(int64_t now) { |
| int disp_time; |
| |
| if (!batt_anim_.run || now < next_screen_transition_) return; |
| |
| // If battery level is not ready, keep checking in the defined time |
| if (health_info_.batteryLevel == 0 && health_info_.batteryStatus == BatteryStatus::UNKNOWN) { |
| if (wait_batt_level_timestamp_ == 0) { |
| // Set max delay time and skip drawing screen |
| wait_batt_level_timestamp_ = now + MAX_BATT_LEVEL_WAIT_TIME; |
| LOGV("[%" PRId64 "] wait for battery capacity ready\n", now); |
| return; |
| } else if (now <= wait_batt_level_timestamp_) { |
| // Do nothing, keep waiting |
| return; |
| } |
| // If timeout and battery level is still not ready, draw unknown battery |
| } |
| |
| if (healthd_draw_ == nullptr) { |
| std::optional<bool> out_screen_on; |
| service()->shouldKeepScreenOn([&](Result res, bool screen_on) { |
| if (res == Result::SUCCESS) { |
| *out_screen_on = screen_on; |
| } |
| }); |
| if (out_screen_on.has_value()) { |
| if (!*out_screen_on) { |
| LOGV("[%" PRId64 "] leave screen off\n", now); |
| batt_anim_.run = false; |
| next_screen_transition_ = -1; |
| if (charger_online()) request_suspend(true); |
| return; |
| } |
| } |
| |
| healthd_draw_.reset(new HealthdDraw(&batt_anim_)); |
| |
| if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) { |
| healthd_draw_->blank_screen(true); |
| screen_blanked_ = true; |
| } |
| } |
| |
| /* animation is over, blank screen and leave */ |
| if (batt_anim_.num_cycles > 0 && batt_anim_.cur_cycle == batt_anim_.num_cycles) { |
| reset_animation(&batt_anim_); |
| next_screen_transition_ = -1; |
| healthd_draw_->blank_screen(true); |
| screen_blanked_ = true; |
| LOGV("[%" PRId64 "] animation done\n", now); |
| if (charger_online()) request_suspend(true); |
| return; |
| } |
| |
| disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time; |
| |
| /* unblank the screen on first cycle and first frame */ |
| if (batt_anim_.cur_cycle == 0 && batt_anim_.cur_frame == 0) healthd_draw_->blank_screen(false); |
| |
| if (screen_blanked_) { |
| healthd_draw_->blank_screen(false); |
| screen_blanked_ = false; |
| } |
| |
| /* animation starting, set up the animation */ |
| if (batt_anim_.cur_frame == 0) { |
| LOGV("[%" PRId64 "] animation starting\n", now); |
| batt_anim_.cur_level = health_info_.batteryLevel; |
| batt_anim_.cur_status = (int)health_info_.batteryStatus; |
| if (health_info_.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; |
| } |
| } |
| |
| if (charger_online()) { |
| // repeat the first frame first_frame_repeats times |
| disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time * |
| batt_anim_.first_frame_repeats; |
| } else { |
| disp_time = UNPLUGGED_DISPLAY_TIME / batt_anim_.num_cycles; |
| } |
| |
| LOGV("cur_frame=%d disp_time=%d\n", batt_anim_.cur_frame, disp_time); |
| } |
| } |
| |
| /* draw the new frame (@ cur_frame) */ |
| healthd_draw_->redraw_screen(&batt_anim_, surf_unknown_); |
| |
| /* 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); |
| next_screen_transition_ = now + BATTERY_UNKNOWN_TIME; |
| batt_anim_.cur_cycle++; |
| return; |
| } |
| |
| /* schedule next screen transition */ |
| next_screen_transition_ = curr_time_ms() + disp_time; |
| |
| /* advance frame cntr to the next valid frame only if we are charging |
| * if necessary, advance cycle cntr, and reset frame cntr |
| */ |
| if (charger_online()) { |
| batt_anim_.cur_frame++; |
| |
| 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++; |
| } |
| 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 |
| */ |
| } |
| } 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++; |
| } |
| } |
| |
| int Charger::SetKeyCallback(int code, int value) { |
| int64_t now = curr_time_ms(); |
| int down = !!value; |
| |
| if (code > KEY_MAX) return -1; |
| |
| /* ignore events that don't modify our state */ |
| if (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) keys_[code].timestamp = now; |
| keys_[code].down = down; |
| keys_[code].pending = true; |
| if (down) { |
| LOGV("[%" PRId64 "] key[%d] down\n", now, code); |
| } else { |
| int64_t duration = now - 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; |
| } |
| |
| void Charger::UpdateInputState(input_event* ev) { |
| if (ev->type != EV_KEY) return; |
| SetKeyCallback(ev->code, ev->value); |
| } |
| |
| void Charger::SetNextKeyCheck(key_state* key, int64_t timeout) { |
| int64_t then = key->timestamp + timeout; |
| |
| if (next_key_check_ == -1 || then < next_key_check_) next_key_check_ = then; |
| } |
| |
| void Charger::ProcessKey(int code, int64_t now) { |
| key_state* key = &keys_[code]; |
| |
| if (code == KEY_POWER) { |
| if (key->down) { |
| int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME; |
| 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 (batt_anim_.cur_level >= boot_min_cap_) { |
| LOGW("[%" PRId64 "] rebooting\n", now); |
| reboot(RB_AUTOBOOT); |
| } 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 |
| */ |
| SetNextKeyCheck(key, POWER_ON_KEY_TIME); |
| |
| /* Turn on the display and kick animation on power-key press |
| * rather than on key release |
| */ |
| kick_animation(&batt_anim_); |
| request_suspend(false); |
| } |
| } else { |
| /* if the power key got released, force screen state cycle */ |
| if (key->pending) { |
| kick_animation(&batt_anim_); |
| request_suspend(false); |
| } |
| } |
| } |
| |
| key->pending = false; |
| } |
| |
| void Charger::HandleInputState(int64_t now) { |
| ProcessKey(KEY_POWER, now); |
| |
| if (next_key_check_ != -1 && now > next_key_check_) next_key_check_ = -1; |
| } |
| |
| void Charger::HandlePowerSupplyState(int64_t now) { |
| int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME; |
| if (!have_battery_state_) return; |
| |
| if (!charger_online()) { |
| request_suspend(false); |
| if (next_pwr_check_ == -1) { |
| /* Last cycle would have stopped at the extreme top of battery-icon |
| * Need to show the correct level corresponding to capacity. |
| * |
| * Reset next_screen_transition_ to update screen immediately. |
| * Reset & kick animation to show complete animation cycles |
| * when charger disconnected. |
| */ |
| timer_shutdown = |
| property_get_int32(UNPLUGGED_SHUTDOWN_TIME_PROP, UNPLUGGED_SHUTDOWN_TIME); |
| next_screen_transition_ = now - 1; |
| reset_animation(&batt_anim_); |
| kick_animation(&batt_anim_); |
| next_pwr_check_ = now + timer_shutdown; |
| LOGW("[%" PRId64 "] device unplugged: shutting down in %" PRId64 " (@ %" PRId64 ")\n", |
| now, (int64_t)timer_shutdown, next_pwr_check_); |
| } else if (now >= next_pwr_check_) { |
| LOGW("[%" PRId64 "] shutting down\n", now); |
| reboot(RB_POWER_OFF); |
| } else { |
| /* otherwise we already have a shutdown timer scheduled */ |
| } |
| } else { |
| /* online supply present, reset shutdown timer if set */ |
| if (next_pwr_check_ != -1) { |
| /* Reset next_screen_transition_ to update screen immediately. |
| * Reset & kick animation to show complete animation cycles |
| * when charger connected again. |
| */ |
| request_suspend(false); |
| next_screen_transition_ = now - 1; |
| reset_animation(&batt_anim_); |
| kick_animation(&batt_anim_); |
| LOGW("[%" PRId64 "] device plugged in: shutdown cancelled\n", now); |
| } |
| next_pwr_check_ = -1; |
| } |
| } |
| |
| /* Follow in ../hardware/qcom/display/liblight/lights.c */ |
| static constexpr const char* rgb_red_blink_file = "/sys/class/leds/red/breath"; |
| static constexpr const char* rgb_red_led_file = "/sys/class/leds/red/brightness"; |
| static constexpr const char* rgb_green_led_file = "/sys/class/leds/green/brightness"; |
| static constexpr const char* rgb_blue_led_file = "/sys/class/leds/blue/brightness"; |
| |
| using light_buffer_t = std::array<char, 128u>; |
| |
| static constexpr unsigned int RGB_ON_LEVEL = 0x7F; |
| static constexpr unsigned int RGB_OFF_LEVEL = 0x00; |
| |
| static int write_level_to_led(unsigned int level, const char* led_file) |
| { |
| const int fd = open(led_file, O_WRONLY); |
| if(fd < 0) |
| { |
| return -errno; |
| } |
| light_buffer_t buffer{}; |
| const int bytes = sprintf(buffer.data(), "%u", level); |
| if (bytes <= 0) { |
| LOGE("Charging RGB: Failed on buffer init!\n"); |
| close(fd); |
| return -1; |
| } |
| const int vRet = write(fd, buffer.data(), static_cast<size_t>(bytes)); |
| LOGV("Charging RGB: Config LED %s(%d)\n", buffer.data(), vRet); |
| const int errno_ = errno; |
| close(fd); |
| if (vRet == -1) { |
| LOGE("Charging RGB: Failed at write(): %d\n", errno_); |
| return -errno_; |
| } |
| return 0; |
| } |
| |
| static int write_rgb_red_led_cfg(unsigned int level) |
| { |
| const int err = write_level_to_led(level, rgb_red_led_file); |
| LOGV("Charging RGB: Config RED LED (%d)\n", err); |
| return err; |
| } |
| |
| static int write_rgb_red_blink_cfg(unsigned int level, unsigned int onMS, unsigned int offMS) |
| { |
| int blink = 0; |
| if (onMS > 0 && offMS > 0) { |
| /******************************* |
| * if ON time == OFF time |
| * use blink mode 2 |
| * else |
| * use blink mode 1 |
| *******************************/ |
| if (onMS == offMS) { |
| blink = 2; |
| } |
| else { |
| blink = 1; |
| } |
| } |
| |
| if (blink) { |
| const int err = write_level_to_led(blink, rgb_red_blink_file); |
| if (err) { |
| LOGE("Charging RGB: Config RED BLINK failed!\n"); |
| return err; |
| } |
| LOGV("Charging RGB: Config RED BLINK (%d)\n", err); |
| return 0; |
| } |
| |
| return write_rgb_red_led_cfg(level); |
| } |
| |
| static int write_rgb_green_led_cfg(unsigned int level) |
| { |
| const int err = write_level_to_led(level, rgb_green_led_file); |
| LOGV("Charging RGB: Config GREEN LED (%d)\n", err); |
| return err; |
| } |
| |
| static int write_rgb_blue_led_cfg(unsigned int level) |
| { |
| const int err = write_level_to_led(level, rgb_blue_led_file); |
| LOGV("Charging RGB: Config BLUE LED (%d)\n", err); |
| return err; |
| } |
| |
| static void charging_rgbled_off() |
| { |
| write_rgb_red_led_cfg(RGB_OFF_LEVEL); |
| write_rgb_green_led_cfg(RGB_OFF_LEVEL); |
| write_rgb_blue_led_cfg(RGB_OFF_LEVEL); |
| } |
| |
| void Charger::charging_rgbled_update() |
| { |
| if (!have_battery_state_) { |
| LOGE("Charging RGB: There is not battery\n"); |
| |
| led_last_battery_level_ = -1; /* Reset record when charger plug out */ |
| charging_rgbled_off(); |
| return; |
| } |
| |
| if (!charger_online()){ |
| LOGV("Charging RGB: unplug charging\n"); |
| |
| led_last_battery_level_ = -1; /* Reset record when charger plug out */ |
| charging_rgbled_off(); |
| return; |
| } |
| |
| const int battery_level = health_info_.batteryLevel; |
| |
| if (led_last_battery_level_ == battery_level) { |
| return; |
| } |
| |
| LOGV("Charging RGB: Battery Level: %d / %d\n", battery_level, led_last_battery_level_); |
| |
| led_last_battery_level_ = battery_level; |
| |
| if (battery_level >= 100) { |
| write_rgb_green_led_cfg(RGB_ON_LEVEL); |
| write_rgb_red_led_cfg(RGB_OFF_LEVEL); |
| } |
| else if (battery_level < 100 && battery_level >= 90) { |
| write_rgb_green_led_cfg(RGB_ON_LEVEL); |
| write_rgb_red_led_cfg(RGB_OFF_LEVEL); |
| } |
| else if (battery_level < 90 && battery_level >= 15) { |
| write_rgb_green_led_cfg(RGB_ON_LEVEL); |
| write_rgb_red_led_cfg(RGB_ON_LEVEL); |
| } |
| else if (battery_level < 15 && battery_level >= 5) { |
| write_rgb_green_led_cfg(RGB_OFF_LEVEL); |
| write_rgb_red_blink_cfg(RGB_ON_LEVEL, 0x00, 0x00); |
| } |
| else { |
| write_rgb_green_led_cfg(RGB_OFF_LEVEL); |
| write_rgb_red_blink_cfg(RGB_ON_LEVEL, 0x00, 0x00); |
| } |
| } |
| |
| void Charger::Heartbeat() { |
| int64_t now = curr_time_ms(); |
| |
| HandleInputState(now); |
| HandlePowerSupplyState(now); |
| |
| /* do screen update last in case any of the above want to start |
| * screen transitions (animations, etc) |
| */ |
| UpdateScreenState(now); |
| charging_rgbled_update(); |
| } |
| |
| void Charger::OnHealthInfoChanged(const HealthInfo_2_1& health_info) { |
| set_charger_online(health_info); |
| |
| if (!have_battery_state_) { |
| have_battery_state_ = true; |
| next_screen_transition_ = curr_time_ms() - 1; |
| request_suspend(false); |
| reset_animation(&batt_anim_); |
| kick_animation(&batt_anim_); |
| } |
| health_info_ = health_info.legacy.legacy; |
| |
| AdjustWakealarmPeriods(charger_online()); |
| } |
| |
| int Charger::PrepareToWait(void) { |
| 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, next_screen_transition_, next_key_check_, next_pwr_check_); |
| |
| if (next_screen_transition_ != -1) next_event = next_screen_transition_; |
| if (next_key_check_ != -1 && next_key_check_ < next_event) next_event = next_key_check_; |
| if (next_pwr_check_ != -1 && next_pwr_check_ < next_event) next_event = next_pwr_check_; |
| |
| if (next_event != -1 && next_event != INT64_MAX) |
| timeout = max(0, next_event - now); |
| else |
| timeout = -1; |
| |
| return (int)timeout; |
| } |
| |
| int Charger::InputCallback(int fd, unsigned int epevents) { |
| input_event ev; |
| int ret; |
| |
| ret = ev_get_input(fd, epevents, &ev); |
| if (ret) return -1; |
| UpdateInputState(&ev); |
| return 0; |
| } |
| |
| static void charger_event_handler(HealthLoop* /*charger_loop*/, uint32_t /*epevents*/) { |
| int ret; |
| |
| ret = ev_wait(-1); |
| if (!ret) ev_dispatch(); |
| } |
| |
| void Charger::InitAnimation() { |
| bool parse_success; |
| |
| std::string content; |
| if (base::ReadFileToString(product_animation_desc_path, &content)) { |
| parse_success = parse_animation_desc(content, &batt_anim_); |
| batt_anim_.set_resource_root(product_animation_root); |
| } else if (base::ReadFileToString(animation_desc_path, &content)) { |
| parse_success = parse_animation_desc(content, &batt_anim_); |
| } else { |
| LOGW("Could not open animation description at %s\n", animation_desc_path); |
| parse_success = false; |
| } |
| |
| if (!parse_success) { |
| LOGW("Could not parse animation description. Using default animation.\n"); |
| batt_anim_ = BASE_ANIMATION; |
| batt_anim_.animation_file.assign("charger/battery_scale"); |
| InitDefaultAnimationFrames(); |
| batt_anim_.frames = owned_frames_.data(); |
| batt_anim_.num_frames = owned_frames_.size(); |
| } |
| if (batt_anim_.fail_file.empty()) { |
| batt_anim_.fail_file.assign("charger/battery_fail"); |
| } |
| |
| LOGV("Animation Description:\n"); |
| LOGV(" animation: %d %d '%s' (%d)\n", batt_anim_.num_cycles, batt_anim_.first_frame_repeats, |
| batt_anim_.animation_file.c_str(), batt_anim_.num_frames); |
| LOGV(" fail_file: '%s'\n", batt_anim_.fail_file.c_str()); |
| LOGV(" clock: %d %d %d %d %d %d '%s'\n", batt_anim_.text_clock.pos_x, |
| batt_anim_.text_clock.pos_y, batt_anim_.text_clock.color_r, batt_anim_.text_clock.color_g, |
| batt_anim_.text_clock.color_b, batt_anim_.text_clock.color_a, |
| batt_anim_.text_clock.font_file.c_str()); |
| LOGV(" percent: %d %d %d %d %d %d '%s'\n", batt_anim_.text_percent.pos_x, |
| batt_anim_.text_percent.pos_y, batt_anim_.text_percent.color_r, |
| batt_anim_.text_percent.color_g, batt_anim_.text_percent.color_b, |
| batt_anim_.text_percent.color_a, batt_anim_.text_percent.font_file.c_str()); |
| for (int i = 0; i < batt_anim_.num_frames; i++) { |
| LOGV(" frame %.2d: %d %d %d\n", i, batt_anim_.frames[i].disp_time, |
| batt_anim_.frames[i].min_level, batt_anim_.frames[i].max_level); |
| } |
| } |
| |
| void Charger::Init(struct healthd_config* config) { |
| int ret; |
| int i; |
| int epollfd; |
| |
| dump_last_kmsg(); |
| |
| LOGW("--------------- STARTING CHARGER MODE ---------------\n"); |
| |
| ret = ev_init( |
| std::bind(&Charger::InputCallback, this, std::placeholders::_1, std::placeholders::_2)); |
| if (!ret) { |
| epollfd = ev_get_epollfd(); |
| RegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD); |
| } |
| |
| InitAnimation(); |
| |
| ret = res_create_display_surface(batt_anim_.fail_file.c_str(), &surf_unknown_); |
| if (ret < 0) { |
| LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret); |
| ret = res_create_display_surface("charger/battery_fail", &surf_unknown_); |
| if (ret < 0) { |
| LOGE("Cannot load built in battery_fail image\n"); |
| surf_unknown_ = NULL; |
| } |
| } |
| |
| for (i = 0; i < batt_anim_.num_frames; i++) { |
| LOGW("[B]%s(%d): frame.name=%s\n", __func__, __LINE__, batt_anim_.frames[i].name); |
| ret = res_create_display_surface(batt_anim_.frames[i].name, &batt_anim_.frames[i].surface); |
| if (ret < 0) { |
| LOGE("Cannot load image %s\n", batt_anim_.frames[i].name); |
| /* TODO: free the already allocated surfaces... */ |
| batt_anim_.num_frames = 0; |
| batt_anim_.num_cycles = 1; |
| } |
| } |
| ev_sync_key_state(std::bind(&Charger::SetKeyCallback, this, std::placeholders::_1, |
| std::placeholders::_2)); |
| |
| next_screen_transition_ = -1; |
| next_key_check_ = -1; |
| next_pwr_check_ = -1; |
| wait_batt_level_timestamp_ = 0; |
| |
| // Retrieve healthd_config from the existing health HAL. |
| HalHealthLoop::Init(config); |
| |
| boot_min_cap_ = config->boot_min_cap; |
| } |
| |
| } // namespace android |
| |
| int healthd_charger_main(int argc, char** argv) { |
| int ch; |
| |
| while ((ch = getopt(argc, argv, "cr")) != -1) { |
| switch (ch) { |
| case 'c': |
| // -c is now a noop |
| break; |
| case 'r': |
| // -r is now a noop |
| break; |
| case '?': |
| default: |
| LOGE("Unrecognized charger option: %c\n", optopt); |
| exit(1); |
| } |
| } |
| |
| Charger charger(GetHealthServiceOrDefault()); |
| return charger.StartLoop(); |
| } |