1/*
2 * Copyright (C) 2013 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#define LOG_TAG "healthd"
18
19#include "healthd.h"
20#include "BatteryMonitor.h"
21#include "BatteryPropertiesRegistrar.h"
22
23#include <dirent.h>
24#include <errno.h>
25#include <fcntl.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <unistd.h>
29#include <batteryservice/BatteryService.h>
30#include <cutils/klog.h>
31#include <utils/String8.h>
32#include <utils/Vector.h>
33
34#define POWER_SUPPLY_SUBSYSTEM "power_supply"
35#define POWER_SUPPLY_SYSFS_PATH "/sys/class/" POWER_SUPPLY_SUBSYSTEM
36
37namespace android {
38
39struct sysfsStringEnumMap {
40    char* s;
41    int val;
42};
43
44static int mapSysfsString(const char* str,
45                          struct sysfsStringEnumMap map[]) {
46    for (int i = 0; map[i].s; i++)
47        if (!strcmp(str, map[i].s))
48            return map[i].val;
49
50    return -1;
51}
52
53int BatteryMonitor::getBatteryStatus(const char* status) {
54    int ret;
55    struct sysfsStringEnumMap batteryStatusMap[] = {
56        { "Unknown", BATTERY_STATUS_UNKNOWN },
57        { "Charging", BATTERY_STATUS_CHARGING },
58        { "Discharging", BATTERY_STATUS_DISCHARGING },
59        { "Not charging", BATTERY_STATUS_NOT_CHARGING },
60        { "Full", BATTERY_STATUS_FULL },
61        { NULL, 0 },
62    };
63
64    ret = mapSysfsString(status, batteryStatusMap);
65    if (ret < 0) {
66        KLOG_WARNING(LOG_TAG, "Unknown battery status '%s'\n", status);
67        ret = BATTERY_STATUS_UNKNOWN;
68    }
69
70    return ret;
71}
72
73int BatteryMonitor::getBatteryHealth(const char* status) {
74    int ret;
75    struct sysfsStringEnumMap batteryHealthMap[] = {
76        { "Unknown", BATTERY_HEALTH_UNKNOWN },
77        { "Good", BATTERY_HEALTH_GOOD },
78        { "Overheat", BATTERY_HEALTH_OVERHEAT },
79        { "Dead", BATTERY_HEALTH_DEAD },
80        { "Over voltage", BATTERY_HEALTH_OVER_VOLTAGE },
81        { "Unspecified failure", BATTERY_HEALTH_UNSPECIFIED_FAILURE },
82        { "Cold", BATTERY_HEALTH_COLD },
83        { NULL, 0 },
84    };
85
86    ret = mapSysfsString(status, batteryHealthMap);
87    if (ret < 0) {
88        KLOG_WARNING(LOG_TAG, "Unknown battery health '%s'\n", status);
89        ret = BATTERY_HEALTH_UNKNOWN;
90    }
91
92    return ret;
93}
94
95int BatteryMonitor::readFromFile(const String8& path, char* buf, size_t size) {
96    char *cp = NULL;
97
98    if (path.isEmpty())
99        return -1;
100    int fd = open(path.string(), O_RDONLY, 0);
101    if (fd == -1) {
102        KLOG_ERROR(LOG_TAG, "Could not open '%s'\n", path.string());
103        return -1;
104    }
105
106    ssize_t count = TEMP_FAILURE_RETRY(read(fd, buf, size));
107    if (count > 0)
108            cp = (char *)memrchr(buf, '\n', count);
109
110    if (cp)
111        *cp = '\0';
112    else
113        buf[0] = '\0';
114
115    close(fd);
116    return count;
117}
118
119BatteryMonitor::PowerSupplyType BatteryMonitor::readPowerSupplyType(const String8& path) {
120    const int SIZE = 128;
121    char buf[SIZE];
122    int length = readFromFile(path, buf, SIZE);
123    BatteryMonitor::PowerSupplyType ret;
124    struct sysfsStringEnumMap supplyTypeMap[] = {
125            { "Unknown", ANDROID_POWER_SUPPLY_TYPE_UNKNOWN },
126            { "Battery", ANDROID_POWER_SUPPLY_TYPE_BATTERY },
127            { "UPS", ANDROID_POWER_SUPPLY_TYPE_AC },
128            { "Mains", ANDROID_POWER_SUPPLY_TYPE_AC },
129            { "USB", ANDROID_POWER_SUPPLY_TYPE_USB },
130            { "USB_DCP", ANDROID_POWER_SUPPLY_TYPE_AC },
131            { "USB_CDP", ANDROID_POWER_SUPPLY_TYPE_AC },
132            { "USB_ACA", ANDROID_POWER_SUPPLY_TYPE_AC },
133            { "Wireless", ANDROID_POWER_SUPPLY_TYPE_WIRELESS },
134            { NULL, 0 },
135    };
136
137    if (length <= 0)
138        return ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
139
140    ret = (BatteryMonitor::PowerSupplyType)mapSysfsString(buf, supplyTypeMap);
141    if (ret < 0)
142        ret = ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
143
144    return ret;
145}
146
147bool BatteryMonitor::getBooleanField(const String8& path) {
148    const int SIZE = 16;
149    char buf[SIZE];
150
151    bool value = false;
152    if (readFromFile(path, buf, SIZE) > 0) {
153        if (buf[0] != '0') {
154            value = true;
155        }
156    }
157
158    return value;
159}
160
161int BatteryMonitor::getIntField(const String8& path) {
162    const int SIZE = 128;
163    char buf[SIZE];
164
165    int value = 0;
166    if (readFromFile(path, buf, SIZE) > 0) {
167        value = strtol(buf, NULL, 0);
168    }
169    return value;
170}
171
172bool BatteryMonitor::update(void) {
173    struct BatteryProperties props;
174    bool logthis;
175
176    props.chargerAcOnline = false;
177    props.chargerUsbOnline = false;
178    props.chargerWirelessOnline = false;
179    props.batteryStatus = BATTERY_STATUS_UNKNOWN;
180    props.batteryHealth = BATTERY_HEALTH_UNKNOWN;
181    props.batteryCurrentNow = INT_MIN;
182    props.batteryChargeCounter = INT_MIN;
183
184    if (!mHealthdConfig->batteryPresentPath.isEmpty())
185        props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
186    else
187        props.batteryPresent = true;
188
189    props.batteryLevel = getIntField(mHealthdConfig->batteryCapacityPath);
190    props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
191
192    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
193        props.batteryCurrentNow = getIntField(mHealthdConfig->batteryCurrentNowPath);
194
195    if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
196        props.batteryChargeCounter = getIntField(mHealthdConfig->batteryChargeCounterPath);
197
198    props.batteryTemperature = getIntField(mHealthdConfig->batteryTemperaturePath);
199
200    const int SIZE = 128;
201    char buf[SIZE];
202    String8 btech;
203
204    if (readFromFile(mHealthdConfig->batteryStatusPath, buf, SIZE) > 0)
205        props.batteryStatus = getBatteryStatus(buf);
206
207    if (readFromFile(mHealthdConfig->batteryHealthPath, buf, SIZE) > 0)
208        props.batteryHealth = getBatteryHealth(buf);
209
210    if (readFromFile(mHealthdConfig->batteryTechnologyPath, buf, SIZE) > 0)
211        props.batteryTechnology = String8(buf);
212
213    unsigned int i;
214
215    for (i = 0; i < mChargerNames.size(); i++) {
216        String8 path;
217        path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
218                          mChargerNames[i].string());
219
220        if (readFromFile(path, buf, SIZE) > 0) {
221            if (buf[0] != '0') {
222                path.clear();
223                path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
224                                  mChargerNames[i].string());
225                switch(readPowerSupplyType(path)) {
226                case ANDROID_POWER_SUPPLY_TYPE_AC:
227                    props.chargerAcOnline = true;
228                    break;
229                case ANDROID_POWER_SUPPLY_TYPE_USB:
230                    props.chargerUsbOnline = true;
231                    break;
232                case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
233                    props.chargerWirelessOnline = true;
234                    break;
235                default:
236                    KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
237                                 mChargerNames[i].string());
238                }
239            }
240        }
241    }
242
243    logthis = !healthd_board_battery_update(&props);
244
245    if (logthis) {
246        char dmesgline[256];
247        snprintf(dmesgline, sizeof(dmesgline),
248                 "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
249                 props.batteryLevel, props.batteryVoltage,
250                 props.batteryTemperature < 0 ? "-" : "",
251                 abs(props.batteryTemperature / 10),
252                 abs(props.batteryTemperature % 10), props.batteryHealth,
253                 props.batteryStatus);
254
255        if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
256            char b[20];
257
258            snprintf(b, sizeof(b), " c=%d", props.batteryCurrentNow / 1000);
259            strlcat(dmesgline, b, sizeof(dmesgline));
260        }
261
262        KLOG_INFO(LOG_TAG, "%s chg=%s%s%s\n", dmesgline,
263                  props.chargerAcOnline ? "a" : "",
264                  props.chargerUsbOnline ? "u" : "",
265                  props.chargerWirelessOnline ? "w" : "");
266    }
267
268    if (mBatteryPropertiesRegistrar != NULL)
269        mBatteryPropertiesRegistrar->notifyListeners(props);
270
271    return props.chargerAcOnline | props.chargerUsbOnline |
272            props.chargerWirelessOnline;
273}
274
275void BatteryMonitor::init(struct healthd_config *hc, bool nosvcmgr) {
276    String8 path;
277
278    mHealthdConfig = hc;
279    DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);
280    if (dir == NULL) {
281        KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH);
282    } else {
283        struct dirent* entry;
284
285        while ((entry = readdir(dir))) {
286            const char* name = entry->d_name;
287
288            if (!strcmp(name, ".") || !strcmp(name, ".."))
289                continue;
290
291            char buf[20];
292            // Look for "type" file in each subdirectory
293            path.clear();
294            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name);
295            switch(readPowerSupplyType(path)) {
296            case ANDROID_POWER_SUPPLY_TYPE_AC:
297            case ANDROID_POWER_SUPPLY_TYPE_USB:
298            case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
299                path.clear();
300                path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
301                if (access(path.string(), R_OK) == 0)
302                    mChargerNames.add(String8(name));
303                break;
304
305            case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
306                if (mHealthdConfig->batteryStatusPath.isEmpty()) {
307                    path.clear();
308                    path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH,
309                                      name);
310                    if (access(path, R_OK) == 0)
311                        mHealthdConfig->batteryStatusPath = path;
312                }
313
314                if (mHealthdConfig->batteryHealthPath.isEmpty()) {
315                    path.clear();
316                    path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH,
317                                      name);
318                    if (access(path, R_OK) == 0)
319                        mHealthdConfig->batteryHealthPath = path;
320                }
321
322                if (mHealthdConfig->batteryPresentPath.isEmpty()) {
323                    path.clear();
324                    path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH,
325                                      name);
326                    if (access(path, R_OK) == 0)
327                        mHealthdConfig->batteryPresentPath = path;
328                }
329
330                if (mHealthdConfig->batteryCapacityPath.isEmpty()) {
331                    path.clear();
332                    path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH,
333                                      name);
334                    if (access(path, R_OK) == 0)
335                        mHealthdConfig->batteryCapacityPath = path;
336                }
337
338                if (mHealthdConfig->batteryVoltagePath.isEmpty()) {
339                    path.clear();
340                    path.appendFormat("%s/%s/voltage_now",
341                                      POWER_SUPPLY_SYSFS_PATH, name);
342                    if (access(path, R_OK) == 0) {
343                        mHealthdConfig->batteryVoltagePath = path;
344                    } else {
345                        path.clear();
346                        path.appendFormat("%s/%s/batt_vol",
347                                          POWER_SUPPLY_SYSFS_PATH, name);
348                        if (access(path, R_OK) == 0)
349                            mHealthdConfig->batteryVoltagePath = path;
350                    }
351                }
352
353                if (mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
354                    path.clear();
355                    path.appendFormat("%s/%s/current_now",
356                                      POWER_SUPPLY_SYSFS_PATH, name);
357                    if (access(path, R_OK) == 0)
358                        mHealthdConfig->batteryCurrentNowPath = path;
359                }
360
361                if (mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
362                    path.clear();
363                    path.appendFormat("%s/%s/charge_counter",
364                                      POWER_SUPPLY_SYSFS_PATH, name);
365                    if (access(path, R_OK) == 0)
366                        mHealthdConfig->batteryChargeCounterPath = path;
367                }
368
369                if (mHealthdConfig->batteryTemperaturePath.isEmpty()) {
370                    path.clear();
371                    path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH,
372                                      name);
373                    if (access(path, R_OK) == 0) {
374                        mHealthdConfig->batteryTemperaturePath = path;
375                    } else {
376                        path.clear();
377                        path.appendFormat("%s/%s/batt_temp",
378                                          POWER_SUPPLY_SYSFS_PATH, name);
379                        if (access(path, R_OK) == 0)
380                            mHealthdConfig->batteryTemperaturePath = path;
381                    }
382                }
383
384                if (mHealthdConfig->batteryTechnologyPath.isEmpty()) {
385                    path.clear();
386                    path.appendFormat("%s/%s/technology",
387                                      POWER_SUPPLY_SYSFS_PATH, name);
388                    if (access(path, R_OK) == 0)
389                        mHealthdConfig->batteryTechnologyPath = path;
390                }
391
392                break;
393
394            case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
395                break;
396            }
397        }
398        closedir(dir);
399    }
400
401    if (!mChargerNames.size())
402        KLOG_ERROR(LOG_TAG, "No charger supplies found\n");
403    if (mHealthdConfig->batteryStatusPath.isEmpty())
404        KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n");
405    if (mHealthdConfig->batteryHealthPath.isEmpty())
406        KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n");
407    if (mHealthdConfig->batteryPresentPath.isEmpty())
408        KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n");
409    if (mHealthdConfig->batteryCapacityPath.isEmpty())
410        KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n");
411    if (mHealthdConfig->batteryVoltagePath.isEmpty())
412        KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n");
413    if (mHealthdConfig->batteryTemperaturePath.isEmpty())
414        KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n");
415    if (mHealthdConfig->batteryTechnologyPath.isEmpty())
416        KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n");
417
418    if (nosvcmgr == false) {
419            mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar(this);
420            mBatteryPropertiesRegistrar->publish();
421    }
422}
423
424}; // namespace android
425