低电量自动关机时无关机动画

低电量自动关机时无关机动画

1. 问题描述

  • DEFECT DESCRIPTION:
    No shutdown animation in the electricity display only 1%.
  • REPRODUCING PROCEDURES:
    电量消耗显示只有1%时,手机突然黑屏关机,没有关机动画,长按power键后手机又可以正常开机使用.(黑屏关机后插上充电器,电量显示为1%)
  • EXPECTED BEHAVIOUR:
    There should a shutdown animation in the electricity display only 1%.

2. 分析

关机动画的逻辑主要是在上层进行控制的,该上层的入口是文件BatteryService.java。系统会通过Binder机制将底层检测的到电池电量传入到属性mBatteryLevel中,然后对属性mBatteryLevel进行if操作。

  • 首先,底层由c/c++来检测电池信息并周期性更新电池状态。
  • 系统通过bingder机制将底层检测到的电池状态传入上层(BatteryService.java)
  • 上层接收底层传来的电池信息,并根据不同的电池状态做出相应的操作,如当电池电量为0时,会播放关机动画。

1.电池信息
更新电池电量的服务位于health守护进程中,其在init.rc中的定义如下:

service healthd /sbin/healthd
    class core
    critical
    seclabel u:r:healthd:s0
    group root system wakelock

而该服务的相关代码位于目录system/core/health下,文件health.cpp的main函数如下:

int main(int argc, char **argv) {
    int ch;
    int ret;
    klog_set_level(KLOG_LEVEL);
    healthd_mode_ops = &android_ops;
    /*通过main函数的参数来判断当前手机状态*/
    if (!strcmp(basename(argv[0]), "charger")) {
    /*从字面意思看 应该是在充电状态*/
        healthd_mode_ops = &charger_ops;
    } else {
        while ((ch = getopt(argc, argv, "cr")) != -1) {
            switch (ch) {
            case 'c':
                healthd_mode_ops = &charger_ops;
                break;
            case 'r':
                healthd_mode_ops = &recovery_ops;
                break;
            case '?':
            default:
                KLOG_ERROR(LOG_TAG, "Unrecognized healthd option: %c\n",
                           optopt);
                exit(1);
            }
        }
    }
    ret = healthd_init();
    /*错误处理*/
    if (ret) {
        KLOG_ERROR("Initialization failed, exiting\n");
        exit(2);
    }
    /*进入循环,也就是说手机从开启该服务开始,之后一直停留在循环中*/
    healthd_mainloop();
    KLOG_ERROR("Main loop terminated, exiting\n");
    return 3;
}

然后看看函数healthd_init()做了些什么事,其代码如下:

static int healthd_init() {
    /*没看懂是什么意思,好像是调用的bionic基础库的方法*/
    epollfd = epoll_create(MAX_EPOLL_EVENTS);
    if (epollfd == -1) {
        KLOG_ERROR(LOG_TAG,
                   "epoll_create failed; errno=%d\n",
                   errno);
        return -1;
    }
    /*看名字该是用电池配置信息进行初始化操作*/
    healthd_board_init(&healthd_config);
    /*应该是设置电池模式之类的操作*/
    healthd_mode_ops->init(&healthd_config);
    /*创建一个定时器*/
    wakealarm_init();
    /*初始化uevent环境*/
    uevent_init();
    /*创建BatteryMonitor对象*/
    gBatteryMonitor = new BatteryMonitor();
    /*对创建的BatteryMonitor对象进行初始化*/
    gBatteryMonitor->init(&healthd_config);
    return 0;
}

然后看看这个定时器怎么创建的,具体代码如下:

static void wakealarm_init(void) {
    wakealarm_fd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK);
    if (wakealarm_fd == -1) {
        KLOG_ERROR(LOG_TAG, "wakealarm_init: timerfd_create failed\n");
        return;
    }
    if (healthd_register_event(wakealarm_fd, wakealarm_event))
        KLOG_ERROR(LOG_TAG,
                   "Registration of wakealarm event failed\n");  wakealarm_set_interval(healthd_config.periodic_chores_interval_fast);
}

用方法timerfd_create()创建一个定时器句柄,然后把它存入到全局变量wakealarm_fd中,函数wakealarm_set_interval()设置超时时间。
然后会创建一个BatteryMonitor对象并调用该对象的init方法,BatteryMonitor文件位于system/core/healthd/BatteryMonitor.cpp,该文件主要是读取电池的各种参数并返回给上层,先来看看该对象的init方法,具体如下:

void BatteryMonitor::init(struct healthd_config *hc) {
    String8 path;
    char pval[PROPERTY_VALUE_MAX];

    mHealthdConfig = hc;
    /*
    #define POWER_SUPPLY_SUBSYSTEM "power_supply"
    #define POWER_SUPPLY_SYSFS_PATH "/sys/class/" POWER_SUPPLY_SUBSYSTEM
    根据宏定义 POWER_SUPPLY_SYSFS_PATH="/sys/class/power_supply"即打开该目录
    ,而这个目录下的文件就是存储了电池的各中信息,如我们需要的电池电量值就在/sys/class/power_supply/battery/capacity文件中
    ,通过命令cat /sys/class/power_supply/battery/capacity就可以查看到当前电池电量值
    */
    DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);
    if (dir == NULL) {
        KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH);
    } else {
        struct dirent* entry;

        while ((entry = readdir(dir))) {
            const char* name = entry->d_name;
            /*过滤掉./目录和../目录*/
            if (!strcmp(name, ".") || !strcmp(name, ".."))
                continue;
            // Look for "type" file in each subdirectory
            path.clear();
            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name);
            switch(readPowerSupplyType(path)) {
            case ANDROID_POWER_SUPPLY_TYPE_AC:
            case ANDROID_POWER_SUPPLY_TYPE_USB:
            case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
                path.clear();
                path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
                if (access(path.string(), R_OK) == 0)
                    mChargerNames.add(String8(name));
                break;

            case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
                mBatteryDevicePresent = true;

                if (mHealthdConfig->batteryStatusPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH,
                                      name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryStatusPath = path;
                }

                if (mHealthdConfig->batteryHealthPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH,
                                      name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryHealthPath = path;
                }

                if (mHealthdConfig->batteryPresentPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH,
                                      name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryPresentPath = path;
                }
                /*此处就是读取电池电量值,并把读到的值保存到mHealthdConfig成员变量中*/
                if (mHealthdConfig->batteryCapacityPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH,
                                      name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryCapacityPath = path;
                }

                if (mHealthdConfig->batteryVoltagePath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/voltage_now",
                                      POWER_SUPPLY_SYSFS_PATH, name);
                    if (access(path, R_OK) == 0) {
                        mHealthdConfig->batteryVoltagePath = path;
                    } else {
                        path.clear();
                        path.appendFormat("%s/%s/batt_vol",
                                          POWER_SUPPLY_SYSFS_PATH, name);
                        if (access(path, R_OK) == 0)
                            mHealthdConfig->batteryVoltagePath = path;
                    }
                }

                if (mHealthdConfig->batteryFullChargePath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/charge_full",
                                      POWER_SUPPLY_SYSFS_PATH, name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryFullChargePath = path;
                }

                if (mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/current_now",
                                      POWER_SUPPLY_SYSFS_PATH, name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryCurrentNowPath = path;
                }

                if (mHealthdConfig->batteryCycleCountPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/cycle_count",
                                      POWER_SUPPLY_SYSFS_PATH, name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryCycleCountPath = path;
                }

                if (mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/current_avg",
                                      POWER_SUPPLY_SYSFS_PATH, name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryCurrentAvgPath = path;
                }

                if (mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/charge_counter",
                                      POWER_SUPPLY_SYSFS_PATH, name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryChargeCounterPath = path;
                }

                if (mHealthdConfig->batteryTemperaturePath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH,
                                      name);
                    if (access(path, R_OK) == 0) {
                        mHealthdConfig->batteryTemperaturePath = path;
                    } else {
                        path.clear();
                        path.appendFormat("%s/%s/batt_temp",
                                          POWER_SUPPLY_SYSFS_PATH, name);
                        if (access(path, R_OK) == 0)
                            mHealthdConfig->batteryTemperaturePath = path;
                    }
                }

                if (mHealthdConfig->batteryTechnologyPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/technology",
                                      POWER_SUPPLY_SYSFS_PATH, name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryTechnologyPath = path;
                }

                if (mHealthdConfig->batteryStatusPath_smb.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/status_smb", POWER_SUPPLY_SYSFS_PATH,
                                      name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryStatusPath_smb = path;
                }

                if (mHealthdConfig->batteryPresentPath_smb.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/present_smb", POWER_SUPPLY_SYSFS_PATH,
                                      name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryPresentPath_smb = path;
                }

                if (mHealthdConfig->batteryCapacityPath_smb.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/capacity_smb", POWER_SUPPLY_SYSFS_PATH,
                                      name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryCapacityPath_smb = path;
                }

                if (mHealthdConfig->batteryAdjustPowerPath.isEmpty()) {
                    path.clear();
                    path.appendFormat("%s/%s/adjust_power",
                                      POWER_SUPPLY_SYSFS_PATH, name);
                    if (access(path, R_OK) == 0)
                        mHealthdConfig->batteryAdjustPowerPath = path;
                }
                break;

            case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
                break;
            }
        }
        closedir(dir);
    }

    // Typically the case for devices which do not have a battery and
    // and are always plugged into AC mains.
    if (!mBatteryDevicePresent) {
        KLOG_WARNING(LOG_TAG, "No battery devices found\n");
        hc->periodic_chores_interval_fast = -1;
        hc->periodic_chores_interval_slow = -1;
        mBatteryFixedCapacity = ALWAYS_PLUGGED_CAPACITY;
        mBatteryFixedTemperature = FAKE_BATTERY_TEMPERATURE;
        mAlwaysPluggedDevice = true;
    } else {
        if (mHealthdConfig->batteryStatusPath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n");
        if (mHealthdConfig->batteryHealthPath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n");
        if (mHealthdConfig->batteryPresentPath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n");
        if (mHealthdConfig->batteryCapacityPath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n");
        if (mHealthdConfig->batteryVoltagePath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n");
        if (mHealthdConfig->batteryTemperaturePath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n");
        if (mHealthdConfig->batteryTechnologyPath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n");
        if (mHealthdConfig->batteryCurrentNowPath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryCurrentNowPath not found\n");
        if (mHealthdConfig->batteryFullChargePath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryFullChargePath not found\n");
        if (mHealthdConfig->batteryCycleCountPath.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryCycleCountPath not found\n");
        if (mHealthdConfig->batteryPresentPath_smb.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryPresentPath_smb not found\n");
        if (mHealthdConfig->batteryCapacityPath_smb.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryCapacityPath_smb not found\n");
        if (mHealthdConfig->batteryStatusPath_smb.isEmpty())
            KLOG_WARNING(LOG_TAG, "BatteryStatusPath_smb not found\n");
    }

    if (property_get("ro.boot.fake_battery", pval, NULL) > 0
                                               && strtol(pval, NULL, 10) != 0) {
        mBatteryFixedCapacity = FAKE_BATTERY_CAPACITY;
        mBatteryFixedCapacity_smb = FAKE_BATTERY_CAPACITY_SMB;
        mBatteryFixedTemperature = FAKE_BATTERY_TEMPERATURE;
    }
}

该方法会打开/sys/class/power_supply目录,该目录中包含了一些子目录,/sys/class/power_supply/battery目录下存放得就是电池信息。在case ANDROID_POWER_SUPPLY_TYPE_BATTERY:情况下会生成与电池相关的文件名,并把对应的信息保存到成员变量中,方便后面读取使用。

然后是main函数进入healthd_mainloop()循环,其代码如下:

static void healthd_mainloop(void) {
    while (1) {
        struct epoll_event events[eventct];
        int nevents;
        int timeout = awake_poll_interval;
        int mode_timeout;
        mode_timeout = healthd_mode_ops->preparetowait();
        if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout))
            timeout = mode_timeout;
        nevents = epoll_wait(epollfd, events, eventct, timeout);
        if (nevents == -1) {
            if (errno == EINTR)
                continue;
            KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failed\n");
            break;
        }
        for (int n = 0; n < nevents; ++n) {
            if (events[n].data.ptr)
            /* (*(void (*)(int))events[n].data.ptr)(events[n].events)这一行代码看起来好狗屎
            ,应该是把events[n].data.ptr(应该是某个函数指针)强制转换为参数为int型的返回值为空的函数的指针
            ,反正是一个指定类型的函数指针,然后再加了*号,表示调用该函数*/
                (*(void (*)(int))events[n].data.ptr)(events[n].events);
        }
        if (!nevents)
            periodic_chores();
        healthd_mode_ops->heartbeat();
    }
    return;
}
static void wakealarm_event(uint32_t /*epevents*/) {
    unsigned long long wakeups;
    if (read(wakealarm_fd, &wakeups, sizeof(wakeups)) == -1) {
        KLOG_ERROR(LOG_TAG, "wakealarm_event: read wakealarm fd failed\n");
        return;
    }
    periodic_chores();
}
static void periodic_chores() {
    healthd_battery_update();
}
void healthd_battery_update(void) {
    // Fast wake interval when on charger (watch for overheat);
    // slow wake interval when on battery (watch for drained battery).
    /*调用BatteryMonitor的update方法更新电池状态*/
   int new_wake_interval = gBatteryMonitor->update() ?
       healthd_config.periodic_chores_interval_fast :
           healthd_config.periodic_chores_interval_slow;
    if (new_wake_interval != wakealarm_wake_interval)
            wakealarm_set_interval(new_wake_interval);
    // During awake periods poll at fast rate.  If wake alarm is set at fast
    // rate then just use the alarm; if wake alarm is set at slow rate then
    // poll at fast rate while awake and let alarm wake up at slow rate when
    // asleep.
    if (healthd_config.periodic_chores_interval_fast == -1)
        awake_poll_interval = -1;
    else
        awake_poll_interval =
            new_wake_interval == healthd_config.periodic_chores_interval_fast ?
                -1 : healthd_config.periodic_chores_interval_fast * 1000;
}

然后查看文件system/core/healthd/BatteryMonitor.cpp中的update,该方法主要是用来读取电池信息,在health的main方法中,会通过health_mainloop()函数周期性调用它。

bool BatteryMonitor::update(void) {
    bool logthis;
    /*每次调用前会把BatteryProperties 对象中的值清空*/
    initBatteryProperties(&props);

    if (!mHealthdConfig->batteryPresentPath.isEmpty())
        props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
    else
        props.batteryPresent = mBatteryDevicePresent;
    /*将保存在mHealthdConfig中的电池信息赋值给BatteryProperties对象props中的各个成员变量,后面还会通过该对象把电池信息发送给上层*/
    props.batteryLevel = mBatteryFixedCapacity ?
        mBatteryFixedCapacity :
        getIntField(mHealthdConfig->batteryCapacityPath);
    props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath);

    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
        props.batteryCurrent = getIntField(mHealthdConfig->batteryCurrentNowPath) / 1000;

    if (!mHealthdConfig->batteryFullChargePath.isEmpty())
        props.batteryFullCharge = getIntField(mHealthdConfig->batteryFullChargePath);

    if (!mHealthdConfig->batteryCycleCountPath.isEmpty())
        props.batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath);

    if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
        props.batteryChargeCounter = getIntField(mHealthdConfig->batteryChargeCounterPath);

    props.batteryTemperature = mBatteryFixedTemperature ?
        mBatteryFixedTemperature :
        getIntField(mHealthdConfig->batteryTemperaturePath);
    update_smb();
    // For devices which do not have battery and are always plugged
    // into power souce.
    if (mAlwaysPluggedDevice) {
        props.chargerAcOnline = true;
        props.batteryPresent = true;
        props.batteryStatus = BATTERY_STATUS_CHARGING;
        props.batteryHealth = BATTERY_HEALTH_GOOD;
    }

    const int SIZE = 128;
    char buf[SIZE];
    String8 btech;

    if (readFromFile(mHealthdConfig->batteryStatusPath, buf, SIZE) > 0)
        props.batteryStatus = getBatteryStatus(buf);

    if (readFromFile(mHealthdConfig->batteryHealthPath, buf, SIZE) > 0)
        props.batteryHealth = getBatteryHealth(buf);

    if (readFromFile(mHealthdConfig->batteryTechnologyPath, buf, SIZE) > 0)
        props.batteryTechnology = String8(buf);

    unsigned int i;
    double MaxPower = 0;

    for (i = 0; i < mChargerNames.size(); i++) {
        String8 path;
        path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
                          mChargerNames[i].string());

        if (readFromFile(path, buf, SIZE) > 0) {
            if (buf[0] != '0') {
                path.clear();
                path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
                                  mChargerNames[i].string());
                switch(readPowerSupplyType(path)) {
                case ANDROID_POWER_SUPPLY_TYPE_AC:
                    props.chargerAcOnline = true;
                    break;
                case ANDROID_POWER_SUPPLY_TYPE_USB:
                    props.chargerUsbOnline = true;
                    break;
                case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
                    props.chargerWirelessOnline = true;
                    break;
                default:
                    KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
                                 mChargerNames[i].string());
                }
                path.clear();
                path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH,
                                  mChargerNames[i].string());
                int ChargingCurrent =
                    (access(path.string(), R_OK) == 0) ? getIntField(path) : 0;

                path.clear();
                path.appendFormat("%s/%s/voltage_max", POWER_SUPPLY_SYSFS_PATH,
                                  mChargerNames[i].string());

                int ChargingVoltage =
                    (access(path.string(), R_OK) == 0) ? getIntField(path) :
                    DEFAULT_VBUS_VOLTAGE;

                double power = ((double)ChargingCurrent / MILLION) *
                        ((double)ChargingVoltage / MILLION);
                if (MaxPower < power) {
                    props.maxChargingCurrent = ChargingCurrent;
                    props.maxChargingVoltage = ChargingVoltage;
                    MaxPower = power;
                }
            }
        }
    }

    logthis = !healthd_board_battery_update(&props);

    if (logthis) {
        char dmesgline[256];
        size_t len;
        if (props.batteryPresent) {
            snprintf(dmesgline, sizeof(dmesgline),
                 "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
                 props.batteryLevel, props.batteryVoltage,
                 props.batteryTemperature < 0 ? "-" : "",
                 abs(props.batteryTemperature / 10),
                 abs(props.batteryTemperature % 10), props.batteryHealth,
                 props.batteryStatus);

            len = strlen(dmesgline);

            if (props.batteryPresent_smb) {
                snprintf(dmesgline, sizeof(dmesgline),
                     "battery l2=%d st2=%d ext=%d",
                     props.batteryLevel_smb, 
                     props.batteryStatus_smb,
                     props.batteryPresent_smb);
            }

            if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
                len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
                                " c=%d", props.batteryCurrent);
            }

            if (!mHealthdConfig->batteryFullChargePath.isEmpty()) {
                len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
                                " fc=%d", props.batteryFullCharge);
            }

            if (!mHealthdConfig->batteryCycleCountPath.isEmpty()) {
                len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
                                " cc=%d", props.batteryCycleCount);
            }
        } else {
            snprintf(dmesgline, sizeof(dmesgline),
                 "battery none");
        }

        len = strlen(dmesgline);

        KLOG_WARNING(LOG_TAG, "%s chg=%s%s%s\n", dmesgline,
                     props.chargerAcOnline ? "a" : "",
                     props.chargerUsbOnline ? "u" : "",
                     props.chargerWirelessOnline ? "w" : "");

        snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s",
                 props.chargerAcOnline ? "a" : "",
                 props.chargerUsbOnline ? "u" : "",
                 props.chargerWirelessOnline ? "w" : "");

        KLOG_WARNING(LOG_TAG, "%s\n", dmesgline);
    }
    cmd_send();
    healthd_mode_ops->battery_update(&props);
    return props.chargerAcOnline | props.chargerUsbOnline |
            props.chargerWirelessOnline;
}

然后是通过binder把电池信息props传入到上层batteryService中,在该Service中有个BatteryListener对象,该类的定义如下:

    private final class BatteryListener extends IBatteryPropertiesListener.Stub {
        @Override public void batteryPropertiesChanged(BatteryProperties props) {
            final long identity = Binder.clearCallingIdentity();
            try {
                BatteryService.this.update(props);
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
       }
    }

然后会把传入的BatteryProperties对象props(里面包含的都是电池信息)通过update方法赋值给mLastBatteryProps属性,update方法具体如下:

private void update(BatteryProperties props) {
        synchronized (mLock) {
            if (!mUpdatesStopped) {
                mBatteryProps = props;
                if (SystemProperties.get("ro.mtk_ipo_support").equals("1")) {
                    if (mIPOShutdown)
                        return;
                }
                // Process the new values.
                if (mBootCompleted)
                processValuesLocked(false);
            } else {
                mLastBatteryProps.set(props);
            }
        }
    }

然后看看BatteryProperties类里到底有些什么,具体如下:

public class BatteryProperties implements Parcelable {
    public boolean chargerAcOnline;
    public boolean chargerUsbOnline;
    public boolean chargerWirelessOnline;
    public int maxChargingCurrent;
    public int maxChargingVoltage;
    public int batteryStatus;
    public int batteryStatus_smb;
    public int batteryHealth;
    public boolean batteryPresent;
    public boolean batteryPresent_smb;
    public int batteryLevel;
    public int batteryLevel_smb;
    public int batteryVoltage;
    public int batteryTemperature;
    public int batteryCurrentNow;
    public int batteryChargeCounter;
    public int adjustPower;
    public String batteryTechnology;

    public BatteryProperties() {
    }

    public void set(BatteryProperties other) {
        chargerAcOnline = other.chargerAcOnline;
        chargerUsbOnline = other.chargerUsbOnline;
        chargerWirelessOnline = other.chargerWirelessOnline;
        maxChargingCurrent = other.maxChargingCurrent;
        maxChargingVoltage = other.maxChargingVoltage;
        batteryStatus = other.batteryStatus;
        batteryHealth = other.batteryHealth;
        batteryPresent = other.batteryPresent;
        batteryLevel = other.batteryLevel;
        batteryVoltage = other.batteryVoltage;
        batteryTemperature = other.batteryTemperature;
        batteryStatus_smb = other.batteryStatus_smb;
        batteryPresent_smb = other.batteryPresent_smb;
        batteryLevel_smb = other.batteryLevel_smb;
        batteryCurrentNow = other.batteryCurrentNow;
        batteryChargeCounter = other.batteryChargeCounter;
        adjustPower = other.adjustPower;
        batteryTechnology = other.batteryTechnology;
    }

    /*
     * Parcel read/write code must be kept in sync with
     * frameworks/native/services/batteryservice/BatteryProperties.cpp
     */

    private BatteryProperties(Parcel p) {
        chargerAcOnline = p.readInt() == 1 ? true : false;
        chargerUsbOnline = p.readInt() == 1 ? true : false;
        chargerWirelessOnline = p.readInt() == 1 ? true : false;
        maxChargingCurrent = p.readInt();
        maxChargingVoltage = p.readInt();
        batteryStatus = p.readInt();
        batteryHealth = p.readInt();
        batteryPresent = p.readInt() == 1 ? true : false;
        batteryLevel = p.readInt();
        batteryVoltage = p.readInt();
        batteryTemperature = p.readInt();
        batteryStatus_smb = p.readInt();
        batteryPresent_smb = p.readInt() == 1 ? true : false;
        batteryLevel_smb = p.readInt();
        batteryCurrentNow = p.readInt();
        batteryChargeCounter = p.readInt();
        adjustPower = p.readInt();
        batteryTechnology = p.readString();
    }

    public void writeToParcel(Parcel p, int flags) {
        p.writeInt(chargerAcOnline ? 1 : 0);
        p.writeInt(chargerUsbOnline ? 1 : 0);
        p.writeInt(chargerWirelessOnline ? 1 : 0);
        p.writeInt(maxChargingCurrent);
        p.writeInt(maxChargingVoltage);
        p.writeInt(batteryStatus);
        p.writeInt(batteryHealth);
        p.writeInt(batteryPresent ? 1 : 0);
        p.writeInt(batteryLevel);
        p.writeInt(batteryVoltage);
        p.writeInt(batteryTemperature);
        p.writeInt(batteryStatus_smb);
        p.writeInt(batteryPresent_smb ? 1 : 0);
        p.writeInt(batteryLevel_smb);
        p.writeInt(batteryCurrentNow);
        p.writeInt(batteryChargeCounter);
        p.writeInt(adjustPower);
        p.writeString(batteryTechnology);
    }

    public static final Parcelable.Creator<BatteryProperties> CREATOR
        = new Parcelable.Creator<BatteryProperties>() {
        public BatteryProperties createFromParcel(Parcel p) {
            return new BatteryProperties(p);
        }

        public BatteryProperties[] newArray(int size) {
            return new BatteryProperties[size];
        }
    };

    public int describeContents() {
        return 0;
    }
}

这个类是个工具类,主要用来打包存放电池信息和为各个电池属性进行赋值操作。然后转入batteryService.java文件。在上层处理关机动画的的流程中,主要根据如下逻辑进行操作的。

Created with Raphaël 2.1.0 Start BatteryService.java中shutdownIfNoPowerLocked()方法 ShutdownActivity.java PowerManagerService.java ShutdownThread.java PowerManager.java End

首先看看batteryService类的构造方法,具体代码如下:

 public BatteryService(Context context) {
        super(context);

        mContext = context;
        mHandler = new Handler(true /*async*/);
        mLed = new Led(context, getLocalService(LightsManager.class));
        mSettingsObserver = new SettingsObserver();
        mBatteryStats = BatteryStatsService.getService();

        mCriticalBatteryLevel = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_criticalBatteryWarningLevel);
        mLowBatteryWarningLevel = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_lowBatteryWarningLevel);
        mLowBatteryCloseWarningLevel = mLowBatteryWarningLevel + mContext.getResources().getInteger(
                com.android.internal.R.integer.config_lowBatteryCloseWarningBump);
        mShutdownBatteryTemperature = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_shutdownBatteryTemperature);

        // watch for invalid charger messages if the invalid_charger switch exists
        if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
            UEventObserver invalidChargerObserver = new UEventObserver() {
                @Override
                public void onUEvent(UEvent event) {
                    final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
                    synchronized (mLock) {
                        if (mInvalidCharger != invalidCharger) {
                            mInvalidCharger = invalidCharger;
                        }
                    }
                }
            };
            invalidChargerObserver.startObserving(
                    "DEVPATH=/devices/virtual/switch/invalid_charger");
        }
    }

构造方法中会先获得BatteryStatsService的对象,BatteryStatsService主要功能是手机系统中各个模块和进程的耗电情况,通过BatteryStatsService来得到相关数据。然后是系统设定各种低电量报警值:

  • mLowBatteryWarningLevel
    这个是电池电量不足,系统将自动发出警报
  • mLowBatteryCloseWarningLevel
    表示停止电量不足警告的值
  • mShutdownBatteryTemperature
    表示电池温度过高,系统将自动关闭
  • mCriticalBatteryLevel
    表示电池电量太低了,低于它会自动关机

然后还会创建一个UEventObserver对象,用来监听UEvent事件,主要监听设备插入无效充电器的事件,如果发生该事件,会调用onUEvent方法。

根据控件生命周期该服务会执行onStart()方法,该方法具体代码如下:

public void onStart() {
        IBinder b = ServiceManager.getService("batteryproperties");
        final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
                IBatteryPropertiesRegistrar.Stub.asInterface(b);
        try {
        /*系统注册监听,实时获得底层传来的电池信息*/
            batteryPropertiesRegistrar.registerListener(new BatteryListener());
        } catch (RemoteException e) {
            // Should never happen.
        }
        /*这个if里面的逻辑实际上不会调用*/
        if (SystemProperties.get("ro.mtk_ipo_support").equals("1")) {
            IntentFilter filter = new IntentFilter();
            filter.addAction(IPO_POWER_ON);
            filter.addAction(IPO_POWER_OFF);
            mContext.registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (IPO_POWER_ON.equals(intent.getAction())) {
                        mIPOShutdown = false;
                        mIPOBoot = true;
                        // Let BatteryService to handle low battery warning.
                        mLastBatteryLevel = mLowBatteryWarningLevel + 1;
                        update(mBatteryProps);
                    } else
                        if (IPO_POWER_OFF.equals(intent.getAction())) {
                            mIPOShutdown = true;
                    }
                }
            }, filter);
        }

        mBinderService = new BinderService();
        publishBinderService("battery", mBinderService);
        publishLocalService(BatteryManagerInternal.class, new LocalService());
    }

该方法中主要是注册一个监听BatteryListener(),接收底层传来的电池信息,并调用 processValuesLocked()方法,BatteryListener类的具体实现前面已经展示过,这里不再累述。接下来看看processValuesLocked方法的具体实现:

 private void processValuesLocked(boolean force) {
        boolean logOutlier = false;
        long dischargeDuration = 0;
        mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel);
        /*设置手机连接充电设备的方法*/
        if (mBatteryProps.chargerAcOnline) {
            mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
        } else if (mBatteryProps.chargerUsbOnline) {
            mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
        } else if (mBatteryProps.chargerWirelessOnline) {
            mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
        } else {
            mPlugType = BATTERY_PLUGGED_NONE;
        }

        /// M: Add for DUAL_INPUT_CHARGER_SUPPORT @{
        if (SystemProperties.get("ro.mtk_diso_support").equals("true")) {
            if (mBatteryProps.chargerAcOnline && mBatteryProps.chargerUsbOnline) {
                mPlugType = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB;
            }
        }
        /// M: @}

        if (DEBUG) {
            Slog.d(TAG, "Processing new values: "
                    + "chargerAcOnline=" + mBatteryProps.chargerAcOnline
                    + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline
                    + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline
                    + ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent
                    + ", maxChargingVoltage" + mBatteryProps.maxChargingVoltage
                    + ", chargeCounter" + mBatteryProps.batteryChargeCounter
                    + ", batteryStatus=" + mBatteryProps.batteryStatus
                    + ", batteryHealth=" + mBatteryProps.batteryHealth
                    + ", batteryPresent=" + mBatteryProps.batteryPresent
                    + ", batteryLevel=" + mBatteryProps.batteryLevel
                    + ", batteryTechnology=" + mBatteryProps.batteryTechnology
                    + ", batteryVoltage=" + mBatteryProps.batteryVoltage
                    + ", batteryTemperature=" + mBatteryProps.batteryTemperature
                    + ", mBatteryLevelCritical=" + mBatteryLevelCritical
                    + ", mPlugType=" + mPlugType);
        }
    if (mLastBatteryVoltage != mBatteryProps.batteryVoltage) {
        Log.d(TAG, "mBatteryVoltage=" + mBatteryProps.batteryVoltage + ", batteryLevel=" + mBatteryProps.batteryLevel_smb);
    }
        // Update the battery LED
        mLed.updateLightsLocked();

        // Let the battery stats keep track of the current level.
        try {
            mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth,
                    mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature,
                    mBatteryProps.batteryVoltage, mBatteryProps.batteryChargeCounter);
        } catch (RemoteException e) {
            // Should never happen.
        }
        /*这里就是判断低电量关机的逻辑*/
        shutdownIfNoPowerLocked();
        shutdownIfOverTempLocked();

        if (force || (mBatteryProps.batteryStatus != mLastBatteryStatus ||
                mBatteryProps.batteryStatus_smb != mLastBatteryStatus_smb ||
                mBatteryProps.batteryHealth != mLastBatteryHealth ||
                mBatteryProps.batteryPresent != mLastBatteryPresent ||
        mBatteryProps.batteryPresent_smb != mLastBatteryPresent_smb ||
                mBatteryProps.batteryLevel != mLastBatteryLevel ||
        mBatteryProps.batteryLevel_smb != mLastBatteryLevel_smb ||
                mPlugType != mLastPlugType ||
                mBatteryProps.batteryVoltage != mLastBatteryVoltage ||
                mBatteryProps.batteryTemperature != mLastBatteryTemperature ||
                mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent ||
                mBatteryProps.maxChargingVoltage != mLastMaxChargingVoltage ||
                mBatteryProps.batteryChargeCounter != mLastChargeCounter ||
                mInvalidCharger != mLastInvalidCharger)) {

            if (mPlugType != mLastPlugType) {
                if (mLastPlugType == BATTERY_PLUGGED_NONE) {
                    // discharging -> charging

                    // There's no value in this data unless we've discharged at least once and the
                    // battery level has changed; so don't log until it does.
                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryProps.batteryLevel) {
                        dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
                        logOutlier = true;
                        EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
                                mDischargeStartLevel, mBatteryProps.batteryLevel);
                        // make sure we see a discharge event before logging again
                        mDischargeStartTime = 0;
                    }
                } else if (mPlugType == BATTERY_PLUGGED_NONE) {
                    // charging -> discharging or we just powered up
                    mDischargeStartTime = SystemClock.elapsedRealtime();
                    mDischargeStartLevel = mBatteryProps.batteryLevel;
                }
            }
            if (mBatteryProps.batteryStatus != mLastBatteryStatus ||
                    mBatteryProps.batteryHealth != mLastBatteryHealth ||
                    mBatteryProps.batteryPresent != mLastBatteryPresent ||
                    mPlugType != mLastPlugType) {
                EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
                        mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, mBatteryProps.batteryPresent ? 1 : 0,
                        mPlugType, mBatteryProps.batteryTechnology);
            }
            if (mBatteryProps.batteryLevel != mLastBatteryLevel) {
                // Don't do this just from voltage or temperature changes, that is
                // too noisy.
                EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
                        mBatteryProps.batteryLevel, mBatteryProps.batteryVoltage, mBatteryProps.batteryTemperature);
            }
            if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
                    mPlugType == BATTERY_PLUGGED_NONE) {
                // We want to make sure we log discharge cycle outliers
                // if the battery is about to die.
                dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
                logOutlier = true;
            }

            if (!mBatteryLevelLow) {
                // Should we now switch in to low battery mode?
                if (mPlugType == BATTERY_PLUGGED_NONE
                        && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel) {
                    mBatteryLevelLow = true;
                }
            } else {
                // Should we now switch out of low battery mode?
                if (mPlugType != BATTERY_PLUGGED_NONE) {
                    mBatteryLevelLow = false;
                } else if (mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel)  {
                    mBatteryLevelLow = false;
                } else if (force && mBatteryProps.batteryLevel >= mLowBatteryWarningLevel) {
                    // If being forced, the previous state doesn't matter, we will just
                    // absolutely check to see if we are now above the warning level.
                    mBatteryLevelLow = false;
                }
            }

            sendIntentLocked();

            // Separate broadcast is sent for power connected / not connected
            // since the standard intent will not wake any applications and some
            // applications may want to have smart behavior based on this.
            if (mPlugType != 0 && mLastPlugType == 0) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
                    }
                });
            }
            else if (mPlugType == 0 && mLastPlugType != 0) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
                    }
                });
            }

            if (shouldSendBatteryLowLocked()) {
                mSentLowBatteryBroadcast = true;
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Intent statusIntent = new Intent(Intent.ACTION_BATTERY_LOW);
                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
                    }
                });
            } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) {
                mSentLowBatteryBroadcast = false;
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
                    }
                });
            }

            if (mBatteryProps.batteryStatus != mLastBatteryStatus &&
                    mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_CMD_DISCHARGING) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                    final String ACTION_IGNORE_DATA_USAGE_ALERT =
                    "android.intent.action.IGNORE_DATA_USAGE_ALERT";

                    Log.d(TAG, "sendBroadcast ACTION_IGNORE_DATA_USAGE_ALERT");
                    Intent statusIntent = new Intent(ACTION_IGNORE_DATA_USAGE_ALERT);
                    statusIntent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
                    mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
                }
            });
            }

            // Update the battery LED
            // mLed.updateLightsLocked();

            // This needs to be done after sendIntent() so that we get the lastest battery stats.
            if (logOutlier && dischargeDuration != 0) {
                logOutlierLocked(dischargeDuration);
            }

            mLastBatteryStatus = mBatteryProps.batteryStatus;
            mLastBatteryStatus_smb = mBatteryProps.batteryStatus_smb;
            mLastBatteryHealth = mBatteryProps.batteryHealth;
            mLastBatteryPresent = mBatteryProps.batteryPresent;
            mLastBatteryPresent_smb = mBatteryProps.batteryPresent_smb;
            mLastBatteryLevel = mBatteryProps.batteryLevel;
            mLastBatteryLevel_smb = mBatteryProps.batteryLevel_smb;
            mLastPlugType = mPlugType;
            mLastBatteryVoltage = mBatteryProps.batteryVoltage;
            mLastBatteryTemperature = mBatteryProps.batteryTemperature;
            mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent;
            mLastMaxChargingVoltage = mBatteryProps.maxChargingVoltage;
            mLastChargeCounter = mBatteryProps.batteryChargeCounter;
            mLastBatteryLevelCritical = mBatteryLevelCritical;
            mLastInvalidCharger = mInvalidCharger;
        }
    }

每次有电池信息改变是,都会调用processValuesLocked这个方法,并通过shutdownIfNoPowerLocked();来判断是否进入低电量关机逻辑。然后看看shutdownIfNoPowerLocked的实现:

 private void shutdownIfNoPowerLocked() {
        // shut down gracefully if our battery is critically low and we are not powered.
        // wait until the system has booted before attempting to display the shutdown dialog.
        /*即判断如果电池电量为0,并且没有连接充电设备 就进入if逻辑中*/
        if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (ActivityManagerNative.isSystemReady()) {
                        if (SystemProperties.get("ro.mtk_ipo_support").equals("1")) {
                            SystemProperties.set("sys.ipo.battlow", "1");
                        }
                        Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                        /*把一些相关信息通过intent,传入目标activity中*/
                        intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                    }
                }
            });
        }
    }
<activity android:name="com.android.internal.app.ShutdownActivity"
            android:permission="android.permission.SHUTDOWN"
            android:theme="@style/Theme.NoDisplay"
            android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.ACTION_REQUEST_SHUTDOWN" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.REBOOT" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

然后查看ShutdownActivity.java的具体实现

public class ShutdownActivity extends Activity {

    private static final String TAG = "ShutdownActivity";
    private boolean mReboot;
    private boolean mConfirm;
    private boolean mUserRequested;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();
        mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
        mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
        mUserRequested = intent.getBooleanExtra(Intent.EXTRA_USER_REQUESTED_SHUTDOWN, false);
        Slog.i(TAG, "onCreate(): confirm=" + mConfirm);

        Thread thr = new Thread("ShutdownActivity") {
            @Override
            public void run() {
                IPowerManager pm = IPowerManager.Stub.asInterface(
                        ServiceManager.getService(Context.POWER_SERVICE));
                try {
                    if (mReboot) {
                        pm.reboot(mConfirm, null, false);
                    } else {
                        pm.shutdown(mConfirm,
                                    mUserRequested ? PowerManager.SHUTDOWN_USER_REQUESTED : null,
                                    false);
                    }
                } catch (RemoteException e) {
                }
            }
        };
        thr.start();
        finish();
        // Wait for us to tell the power manager to shutdown.
        try {
            thr.join();
        } catch (InterruptedException e) {
        }
    }
}

在该activity中,首先会得到intent中的附件信息,然后创建一个线程,在线程中获得一个IPowerManager对象,并调用它的相关方法。然后转到PowerManagerService.java,PowerManagerService继承了SystemService,它的shutdown方法具体实现如下:

  public void shutdown(boolean confirm, String reason, boolean wait) {            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
            final long ident = Binder.clearCallingIdentity();
            try {
                shutdownOrRebootInternal(HALT_MODE_SHUTDOWN, confirm, reason, wait);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
            final String reason, boolean wait) {
        if (mHandler == null || !mSystemReady) {
            throw new IllegalStateException("Too early to call shutdown() or reboot()");
        }
        /*创建一个线程来做关机出来*/
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                /*选择重启或关机*/
                    if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) {
                        ShutdownThread.rebootSafeMode(mContext, confirm);
                    } else if (haltMode == HALT_MODE_REBOOT) {
                        ShutdownThread.reboot(mContext, reason, confirm);
                    } else {
                        ShutdownThread.shutdown(mContext, reason, confirm);
                    }
                }
            }
        };

        // ShutdownThread must run on a looper capable of displaying the UI.
        Message msg = Message.obtain(mHandler, runnable);
        msg.setAsynchronous(true);
        /*把线程对象通过Message发出去??*/
        mHandler.sendMessage(msg);

        // PowerManager.reboot() is documented not to return so just wait for the inevitable.
        if (wait) {
            synchronized (runnable) {
                while (true) {
                    try {
                        runnable.wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
        }
    }

然后查看ShutdownThread.java的shutdown方法,其实现如下:

public static void shutdown(final Context context, String reason, boolean confirm) {
        mReboot = false;
        mRebootSafeMode = false;
        mReason = reason;

        Log.d(TAG, "!!! Request to shutdown !!!");

        if (mSpew) {
            StackTraceElement[] stack = new Throwable().getStackTrace();
            for (StackTraceElement element : stack)
            {
                Log.d(TAG, "    |----" + element.toString());
            }
        }

        if (SystemProperties.getBoolean("ro.monkey", false)) {
            Log.d(TAG, "Cannot request to shutdown when Monkey is running, returning.");
            return;
        }

        shutdownInner(context, confirm);
    }

    static void shutdownInner(final Context context, boolean confirm) {
        // ensure that only one thread is trying to power down.
        // any additional calls are just returned
        synchronized (sIsStartedGuard) {
            if (sIsStarted) {
                Log.d(TAG, "Request to shutdown already running, returning.");
                return;
            }
        }
        bConfirmForAnimation = confirm;

        final int longPressBehavior = context.getResources().getInteger(
                        com.android.internal.R.integer.config_longPressOnPowerBehavior);
        final int resourceId = mRebootSafeMode
                ? com.android.internal.R.string.reboot_safemode_confirm
                : (mReboot
                ? com.android.internal.R.string.reboot_confirm
                        : com.android.internal.R.string.shutdown_confirm);

        Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
        /*低电量关机流程时,confirm==false,因此走else分支*/
        if (confirm) {
            final CloseDialogReceiver closer = new CloseDialogReceiver(context);
            if (sConfirmDialog != null) {
                sConfirmDialog.dismiss();
            }
            bConfirmForAnimation = confirm;
            Log.d(TAG, "PowerOff dialog doesn't exist. Create it first");
            sConfirmDialog = new AlertDialog.Builder(context)
                .setTitle(mRebootSafeMode
                        ? com.android.internal.R.string.reboot_safemode_title
                        : (mReboot
                            ? com.android.internal.R.string.reboot_title
                            : com.android.internal.R.string.power_off))

                        /*OLD : com.android.internal.R.string.power_off)*/
                .setMessage(resourceId)
                .setPositiveButton(mReboot
                        ? com.android.internal.R.string.yes
                        : com.android.internal.R.string.power_off_confirm_bt,
                        new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        beginShutdownSequence(context);
                    }
                })
                .setNegativeButton(com.android.internal.R.string.no, null)
                .create();
            closer.dialog = sConfirmDialog;
            sConfirmDialog.setOnDismissListener(closer);
            sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
            sConfirmDialog.show();
        } else {/*进入低电量关机流程*/
            beginShutdownSequence(context);
        }
    }
  private static void beginShutdownSequence(Context context) {
        Intent newIntent = new Intent("begin.Shutdown.Sequence");
        context.sendBroadcast(newIntent);
        synchronized (sIsStartedGuard) {
            if (sIsStarted) {
                Log.d(TAG, "Shutdown sequence already running, returning.");
                return;
            }
            sIsStarted = true;
        }
        // Throw up a system dialog to indicate the device is rebooting / shutting down.
        /*此处应该是创建Dialog,如正常关机前 会让用户选择是重启 ?关机? 飞行模式?等等 ,不过低电量关机 和这个Dialog 没什么关系*/
        ProgressDialog pd = new ProgressDialog(context);
        // Path 1: Reboot to recovery for update
        //   Condition: mReason == REBOOT_RECOVERY_UPDATE
        //
        //  Path 1a: uncrypt needed
        //   Condition: if /cache/recovery/uncrypt_file exists but
        //              /cache/recovery/block.map doesn't.
        //   UI: determinate progress bar (mRebootHasProgressBar == True)
        //
        // * Path 1a is expected to be removed once the GmsCore shipped on
        //   device always calls uncrypt prior to reboot.
        //
        //  Path 1b: uncrypt already done
        //   UI: spinning circle only (no progress bar)
        //
        // Path 2: Reboot to recovery for factory reset
        //   Condition: mReason == REBOOT_RECOVERY
        //   UI: spinning circle only (no progress bar)
        //
        // Path 3: Regular reboot / shutdown
        //   Condition: Otherwise
        //   UI: spinning circle only (no progress bar)
        /*又是一大拨if 判断 */
        if (PowerManager.REBOOT_RECOVERY_UPDATE.equals(mReason)) {
            // We need the progress bar if uncrypt will be invoked during the
            // reboot, which might be time-consuming.
            mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
                    && !(RecoverySystem.BLOCK_MAP_FILE.exists());
            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
            if (mRebootHasProgressBar) {
                pd.setMax(100);
                pd.setProgress(0);
                pd.setIndeterminate(false);
                pd.setProgressNumberFormat(null);
                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_update_prepare));
                pd.setCancelable(false);
                pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
                pd.show();
                sInstance.mProgressDialog = pd;
            } else {
                pd.setIndeterminate(true);
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_update_reboot));
            }
        } else if (PowerManager.REBOOT_RECOVERY.equals(mReason)) {
            // Factory reset path. Set the dialog message accordingly.
            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
            pd.setMessage(context.getText(
                        com.android.internal.R.string.reboot_to_reset_message));
            pd.setIndeterminate(true);
        } else {
            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
            pd.setIndeterminate(true);
        }
        pd.setCancelable(false);
        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);

        // start the thread that initiates shutdown
        sInstance.mContext = context;
        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
        sInstance.mHandler = new Handler() {
        };

        beginAnimationTime = 0;
        /*关机动画客制化*/
        boolean mShutOffAnimation = configShutdownAnimation(context);
        /*即获得关机时 动画的播放时间???*/
        int screenTurnOffTime = getScreenTurnOffTime(context);
        synchronized (mEnableAnimatingSync) {
            if (mEnableAnimating) {
               if(mRebootHasProgressBar){
                    pd.show();
                    screenTurnOffTime += MAX_UNCRYPT_WAIT_TIME;
                    sInstance.mProgressDialog = pd;
               }else{
               /*低电量关机会走这个分支*/
                    Log.d(TAG, "mIBootAnim.isCustBootAnim() is true");
                    /*这里调用 bootanimCust方法来播放关机动画,实际上是开启一个播放关机动画的服务*/
                    bootanimCust(context);
               }
                sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime);
            }
        }

        // make sure we never fall asleep again
        sInstance.mCpuWakeLock = null;
        try {
            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
            sInstance.mCpuWakeLock.setReferenceCounted(false);
            sInstance.mCpuWakeLock.acquire();
        } catch (SecurityException e) {
            Log.w(TAG, "No permission to acquire wake lock", e);
            sInstance.mCpuWakeLock = null;
        }

        // also make sure the screen stays on for better user experience
        sInstance.mScreenWakeLock = null;
        if (sInstance.mPowerManager.isScreenOn()) {
            try {
                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
                sInstance.mScreenWakeLock.setReferenceCounted(false);
                sInstance.mScreenWakeLock.acquire();
            } catch (SecurityException e) {
                Log.w(TAG, "No permission to acquire wake lock", e);
                sInstance.mScreenWakeLock = null;
            }
        }

        // also make sure the screen stays on for better user experience
        //sInstance.mScreenWakeLock = null;
        if (sInstance.mPowerManager.isScreenOn()) {
            // if Screen old state is on, keep it on
            if (sInstance.mScreenWakeLock == null) {
                Log.d(TAG, "Screen old state is on, keep screen on !");
                try {
                    sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
                            PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
                    sInstance.mScreenWakeLock.setReferenceCounted(false);
                    sInstance.mScreenWakeLock.acquire();
                } catch (SecurityException e) {
                    Log.w(TAG, "No permission to acquire wake lock", e);
                    sInstance.mScreenWakeLock = null;
                }
            } else {
                Log.d(TAG, "Screen is already wake up !");
            }
        } else {
            // WARING: if system is in low power level, pls charge your phone immediately
            Log.d(TAG, "First time wake up Screen failed, try to wake up Screen second time !");
            wakeUpScreen();
        }

        if (sInstance.getState() != Thread.State.NEW || sInstance.isAlive()) {
            if (sInstance.mShutdownFlow == IPO_SHUTDOWN_FLOW) {
                Log.d(TAG, "ShutdownThread exists already");
                checkShutdownFlow();
                synchronized (mShutdownThreadSync) {
                    mShutdownThreadSync.notify();
                }
            } else {
                Log.e(TAG, "Thread state is not normal! froce to shutdown!");
                delayForPlayAnimation();
                //unmout data/cache partitions while performing shutdown
                sInstance.mPowerManager.goToSleep(SystemClock.uptimeMillis(),
                        PowerManager.GO_TO_SLEEP_REASON_SHUTDOWN, 0);
                PowerManagerService.lowLevelShutdown(mReason);
            }
        } else {
        /*开启线程 关闭系统的所有服务*/
            sInstance.start();
        }
    }

beginShutdownSequence主要处理关机过程中的一系列流程,通过调用bootanimCust(context)来开机播放关机动画的服务,然后会计算关机动画时间并保证这个时间中屏幕不会熄灭,同时开启一个sInstance线程来关闭系统中的服务。bootanimCust方法现象代码如下:

private static void bootanimCust(Context context) {
        // [MTK] fix shutdown animation timing issue
        //==================================================================
        SystemProperties.set("service.shutanim.running", "0");
        Log.i(TAG, "set service.shutanim.running to 0");
        //==================================================================
        boolean isRotaionEnabled = false;
        try {
            isRotaionEnabled = Settings.System.getInt(context.getContentResolver(),
                    Settings.System.ACCELEROMETER_ROTATION, 1) != 0;
            if (isRotaionEnabled) {
                final IWindowManager wm = IWindowManager.Stub.asInterface(
                        ServiceManager.getService(Context.WINDOW_SERVICE));
                if (wm != null) {
                    wm.freezeRotation(Surface.ROTATION_0);
                }
                Settings.System.putInt(context.getContentResolver(),
                        Settings.System.ACCELEROMETER_ROTATION, 0);
                Settings.System.putInt(context.getContentResolver(),
                        Settings.System.ACCELEROMETER_ROTATION_RESTORE, 1);
            }
        } catch (NullPointerException ex) {
            Log.e(TAG, "check Rotation: context object is null when get Rotation");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        beginAnimationTime = SystemClock.elapsedRealtime() + MIN_SHUTDOWN_ANIMATION_PLAY_TIME;
        try {
            final IWindowManager wm = IWindowManager.Stub.asInterface(
                    ServiceManager.getService(Context.WINDOW_SERVICE));
            if (wm != null) {
                wm.setEventDispatching(false);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        startBootAnimation();
    }
 private static void startBootAnimation() {
        Log.d(TAG, "Set 'service.bootanim.exit' = 0).");
        SystemProperties.set("service.bootanim.exit", "0");

        if (bPlayaudio) {
            /*在文件ServiceManager.cpp中有如下注释
             - Starting a service is done by writing its name to the "ctl.start" system property. This triggers the init daemon to actually start
the service for us.
大概意思就是向ctl.start属性中写入某个服务名,触发器会开启该服务吧(英文不好 反正大概知道是会开启某个服务的意思)
            Stopping the service is done by writing its name to "ctl.stop"
in a similar way
            低电量关机进入if逻辑中  此处会开启一个服务来播放关机动画*/
            SystemProperties.set("ctl.start", "banim_shutmp3");
            Log.d(TAG, "bootanim:shut mp3");
        } else {
            //SystemProperties.set("ctl.start", "bootanim:shut nomp3");
            SystemProperties.set("ctl.start", "banim_shutnomp3");
            Log.d(TAG, "bootanim:shut nomp3");
        }
    }

然后SystemProperties.set("ctl.start", "banim_shutmp3");到底会开启什么服务呢?通过文件frameworks/base/cmds/bootanimation/bootanim.rc可以看到如下代码:

service bootanim /system/bin/bootanimation
    class core
    user graphics
    group graphics audio
    disabled
    oneshot

# MTK add
service banim_shutmp3 /system/bin/bootanimation shut mp3
    class core
    user graphics
    group graphics audio
    disabled
    oneshot

service banim_shutnomp3 /system/bin/bootanimation shut nomp3
    class core
    user graphics
    group graphics audio
    disabled
    oneshot

所以会开启banim_shutmp3这个服务器,该服务的可执行文件为/system/bin/bootanimation,这个服务就是播放关机动画的服务了,该服务的实现代码在frameworks/base/cmds/bootanimation/目录下。
首先查看下frameworks/base/cmds/bootanimation/bootanimation_main.cpp具体代码:

int main(int argc, char** argv)
{
    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);

    char value[PROPERTY_VALUE_MAX];
    /*获得debug.sf.nobootanimation的值 存入value属性中 如果为1 不会进入下面的if中即不会播放动画*/
    property_get("debug.sf.nobootanimation", value, "0");
    int noBootAnimation = atoi(value);
    ALOGI_IF(noBootAnimation,  "boot animation disabled");
    if (!noBootAnimation) {

        sp<ProcessState> proc(ProcessState::self());
        ProcessState::self()->startThreadPool();

/* M: use our construction method, because we support shut down animation @{
        // create the boot animation object
        sp<BootAnimation> boot = new BootAnimation();

@} */
        bool setBoot = true;
        bool setRotated = false;
        bool sePaly = true;
        /*因为传入了2个参数shut和mp3 */
        if(argc > 1){
           if(!strcmp(argv[1],"shut"))
           /*会进入该逻辑*/
               setBoot = false;
        }
        if(argc > 2){
            if(!strcmp(argv[2],"nomp3"))
               sePaly = false;
        }
        if(argc > 3){
            if(!strcmp(argv[3],"rotate"))
               setRotated = true;
        }
        ALOGD("[BootAnimation %s %d]setBoot=%d,sePaly=%d,setRotated=%d",__FUNCTION__,__LINE__,setBoot,sePaly,setRotated);
        char volume[PROPERTY_VALUE_MAX];
        int LoopCounter = 60;
        int nVolume = -1;
         while(LoopCounter-- > 0 && (nVolume == -1)){
         property_get("persist.sys.mute.state", volume, "-1");
             nVolume = atoi(volume);
             ALOGD("BootAnimation  wait for sample ready, sleep 100ms");
             usleep(100 * 1000);
         }
        ALOGD("[BootAnimation %s %d]nVolume=%d",__FUNCTION__,__LINE__,nVolume);
        if(nVolume == 0 || nVolume == 1 ){
            sePaly = false;
        }
        ALOGD("before new BootAnimation...");
        ALOGD("[BootAnimation %s %d]before new BootAnimation...",__FUNCTION__,__LINE__);
        /*创建一个BootAnimation对象  其中 参数值setBoot=0,sePaly=1,setRotated=0*/
        sp<BootAnimation> boot = new BootAnimation(setBoot,sePaly,setRotated);
        ALOGD("joinThreadPool...");
        ALOGD("[BootAnimation %s %d]before joinThreadPool...",__FUNCTION__,__LINE__);
        IPCThreadState::self()->joinThreadPool();

    }
    ALOGD("[BootAnimation %s %s %d]end",__FILE__,__FUNCTION__,__LINE__);
    return 0;
}

然后看看BootAnimation类,该类的声明如下:

class BootAnimation : public Thread, public IBinder::DeathRecipient
{
public:
                BootAnimation();
                BootAnimation(bool bSetBootOrShutDown,bool bSetPlayMP3,bool bSetRotated);
    virtual     ~BootAnimation();
    void        setBootVideoPlayState(int playState);

    sp<SurfaceComposerClient> session() const;

private:
    virtual bool        threadLoop();
    virtual status_t    readyToRun();
    virtual void        onFirstRef();
    virtual void        binderDied(const wp<IBinder>& who);

    struct Texture {
        GLint   w;
        GLint   h;
        GLuint  name;
    };

    struct Animation {
        struct Frame {
            String8 name;
            FileMap* map;
            String8 fullPath;
            mutable GLuint tid;
            bool operator < (const Frame& rhs) const {
                return name < rhs.name;
            }
        };
        struct Part {
            int count;  // The number of times this part should repeat, 0 for infinite
            int pause;  // The number of frames to pause for at the end of this part
            int clockPosY;  // The y position of the clock, in pixels, from the bottom of the
                            // display (the clock is centred horizontally). -1 to disable the clock
            String8 path;
            SortedVector<Frame> frames;
            bool playUntilComplete;
            float backgroundColor[3];
            FileMap* audioFile;
            Animation* animation;
        };
        int fps;
        int width;
        int height;
        Vector<Part> parts;
        String8 audioConf;
        String8 fileName;
        ZipFileRO* zip;
    };

    status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
    status_t initTexture(const Animation::Frame& frame);
    bool android();
    bool movie();
    status_t initTexture(const SkBitmap* bitmap);
    bool gifMovie();
    void drawTime(const Texture& clockTex, const int yPos);
    Animation* loadAnimation(const String8&);
    bool playAnimation(const Animation&);
    void releaseAnimation(Animation*) const;
    bool parseAnimationDesc(Animation&);
    bool preloadZip(Animation &animation);

    void checkExit();

    sp<SurfaceComposerClient>       mSession;
    sp<AudioPlayer>                 mAudioPlayer;
    AssetManager mAssets;
    Texture     mAndroid[2];
    Texture     mClock;
    int         mWidth;
    int         mHeight;
    EGLDisplay  mDisplay;
    EGLDisplay  mContext;
    EGLDisplay  mSurface;
    sp<SurfaceControl> mFlingerSurfaceControl;
    sp<Surface> mFlingerSurface;

    bool        mClockEnabled;
    String8     mZipFileName;
    SortedVector<String8> mLoadedFiles;

    ZipFileRO   *mZip;
    status_t initTexture(const char* EntryName);
    void initBootanimationZip(char* naimationname);
    void initShutanimationZip(char* naimationname);
    const char* initAudioPath();
    bool ETC1movie();
    void initShader();
    GLuint buildShader(const char* source, GLenum shaderType);
    GLuint buildProgram (const char* vertexShaderSource, const char* fragmentShaderSource);

    bool bBootOrShutDown;
    bool bShutRotate;
    bool bPlayMP3;
    GLuint mProgram;
    GLint mAttribPosition;
    GLint mAttribTexCoord;
    GLint mUniformTexture;
    bool bETC1Movie;
    int mBootVideoPlayType;
    int mBootVideoPlayState;
    SkMovie*    mSkMovie;
    Texture     mJRD;
    int mMcc;
    int mMnc;
    int mSpn;
    bool isTelefonica;
    char mMccMnc[PROPERTY_VALUE_MAX];
    char mOperator[PROPERTY_VALUE_MAX];
    bool mEnableSsv;
    bool mIsValidMccMnc;
    char mSsvAnimationPath[PROPERTY_VALUE_MAX];
    void getSsvAnimationPath(char * ssvAnimationPath,const char* mccmnc,const char* animationName);
    void initSsv();
};

3. solution

通过查看关机log,发现电量值为0的时候,方法shutdownIfNoPowerLocked()通过binder接受到从底层传入的电池电量值并不为0,即条件判断if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY))为false。所以后面的播放关机动画的逻辑是不会走的,而电池的实际电量值已经为0了,所以才会没有播放关机动画就黑屏关机。
既然找到了问题,所以查看为什么在电池电量值为0的时候,上层收到mBatteryProps.batteryLevel属性并不为0呢。在文件battery_common_fg_20.c中函数mt_battery_update_EM实现如下:

static void mt_battery_update_EM(struct battery_data *bat_data)
{
    if((BMT_status.UI_SOC2 <=0)&&(BMT_status.bat_vol >= SYSTEM_OFF_VOLTAGE ))
        bat_data->BAT_CAPACITY = 1;
    else
        bat_data->BAT_CAPACITY = BMT_status.UI_SOC2;
    bat_data->BAT_TemperatureR = BMT_status.temperatureR;    /* API */
    bat_data->BAT_TempBattVoltage = BMT_status.temperatureV;    /* API */
    bat_data->BAT_InstatVolt = BMT_status.bat_vol;    /* VBAT */
    bat_data->BAT_BatteryAverageCurrent = BMT_status.ICharging;
    bat_data->BAT_BatterySenseVoltage = BMT_status.bat_vol;
    bat_data->BAT_ISenseVoltage = BMT_status.Vsense;    /* API */
    bat_data->BAT_ChargerVoltage = BMT_status.charger_vol;
    bat_data->BAT_batt_id = BMT_status.id_vol; 
    /* Dual battery */
    bat_data->status_smb = g_status_smb;
    bat_data->capacity_smb = g_capacity_smb;
    bat_data->present_smb = g_present_smb;
    battery_log(BAT_LOG_FULL, "status_smb = %d, capacity_smb = %d, present_smb = %d\n",
            bat_data->status_smb, bat_data->capacity_smb, bat_data->present_smb);
    if ((BMT_status.UI_SOC2 == 100) && (BMT_status.charger_exist == KAL_TRUE)
        && (BMT_status.bat_charging_state != CHR_ERROR))
        bat_data->BAT_STATUS = POWER_SUPPLY_STATUS_FULL;

#ifdef CONFIG_MTK_DISABLE_POWER_ON_OFF_VOLTAGE_LIMITATION
    if (bat_data->BAT_CAPACITY <= 0)
        bat_data->BAT_CAPACITY = 1;

    battery_log(BAT_LOG_CRTI,
            "BAT_CAPACITY=1, due to define CONFIG_MTK_DISABLE_POWER_ON_OFF_VOLTAGE_LIMITATION\r\n");
#endif
}

在该函数中就是将电池驱动的信息写入对应节点的操作,属性BMT_status.UI_SOC2就是对应电池的实际电量值。在该if条件语句中,首先判断实际电量值是否低于或等于0,如果是ture就继续判断电池电压值BMT_status.bat_vol是否高于设定关机电压,如果电量值为0但是电压值还是过高(高于SYSTEM_OFF_VOLTAGE),会将bat_data->BAT_CAPACITY设定为1,即不进行关机操作,会让实际电压值继续降一段时间。等到同时满足BMT_status.UI_SOC2 =0而且电压值BMT_status.bat_vol低于设定的关机电压时,进入else逻辑中,即让属性bat_data->BAT_CAPACITY = BMT_status.UI_SOC2;即bat_data->BAT_CAPACITY置为0,然后将bat_data->BAT_CAPACITY值写入节点/sys/class/power_supply/battery/capacity。然后BatteryMonitor.cpp会读取该节点,然后将读到的电量值props.batteryLevel传入上层。
导致本问题出现的原因是设置的关机电压值BMT_status.bat_vol过低,在达到低电量关机条件的时候BMT_status.UI_SOC2 <=0)&&(BMT_status.bat_vol >= SYSTEM_OFF_VOLTAGE依然为true,所以传到上层的bat_data->BAT_CAPACITY不为0,无播放关机动画。
最后需要修改battery_common.h文件中的宏定义#define SYSTEM_OFF_VOLTAGE,把它的值调整的高一点即可,具体修改到多少,可根据具体情况而定。

4. 总结

这个问题解决,首先需要了解电池电量值的读取、传递的逻辑,手机中的电量值会通过底层电池驱动把电池信息写入对应的节点中,然后通过BatteryMonitor.cpp来读取相关信息,然后通过binder机制将电池信息不间断的传到上层,然后上层通过获得的电池信息值来做出相关操作,如电量低时通过广播来发送警告,如果电池电量值非常低时,进行关机操作。同时在关机操作前,还会开机线程来对系统中的所有服务进行关闭操作。至于关机动画则是通过开启一个播放关机动画的服务来处理。通过关机log来定位问题,发现在电量值为0的时候底层通过binder传入到上层的电量值不是0,所以不会进入上层播放关机动画的逻辑中来,而底层检测到电量低,则直接黑屏关机处理了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值