/*
|
* Copyright (C) 2014 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
#define LOG_TAG "BatteryStatsService"
|
//#define LOG_NDEBUG 0
|
|
#include <climits>
|
#include <errno.h>
|
#include <fcntl.h>
|
#include <inttypes.h>
|
#include <semaphore.h>
|
#include <stddef.h>
|
#include <stdio.h>
|
#include <string.h>
|
#include <sys/stat.h>
|
#include <sys/types.h>
|
#include <unistd.h>
|
#include <unordered_map>
|
#include <utility>
|
|
#include <android/hardware/power/1.0/IPower.h>
|
#include <android/hardware/power/1.1/IPower.h>
|
#include <android/hardware/power/stats/1.0/IPowerStats.h>
|
#include <android/system/suspend/BnSuspendCallback.h>
|
#include <android/system/suspend/ISuspendControlService.h>
|
#include <android_runtime/AndroidRuntime.h>
|
#include <jni.h>
|
|
#include <nativehelper/ScopedLocalRef.h>
|
#include <nativehelper/ScopedPrimitiveArray.h>
|
|
#include <log/log.h>
|
#include <utils/misc.h>
|
#include <utils/Log.h>
|
|
using android::hardware::Return;
|
using android::hardware::Void;
|
using android::system::suspend::BnSuspendCallback;
|
using android::hardware::power::V1_0::PowerStatePlatformSleepState;
|
using android::hardware::power::V1_0::PowerStateVoter;
|
using android::hardware::power::V1_0::Status;
|
using android::hardware::power::V1_1::PowerStateSubsystem;
|
using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
|
using android::hardware::hidl_vec;
|
using android::system::suspend::ISuspendControlService;
|
using IPowerV1_1 = android::hardware::power::V1_1::IPower;
|
using IPowerV1_0 = android::hardware::power::V1_0::IPower;
|
|
namespace android
|
{
|
|
#define LAST_RESUME_REASON "/sys/kernel/wakeup_reasons/last_resume_reason"
|
#define MAX_REASON_SIZE 512
|
|
static bool wakeup_init = false;
|
static sem_t wakeup_sem;
|
extern sp<IPowerV1_0> getPowerHalV1_0();
|
extern sp<IPowerV1_1> getPowerHalV1_1();
|
extern bool processPowerHalReturn(const Return<void> &ret, const char* functionName);
|
extern sp<ISuspendControlService> getSuspendControl();
|
|
// Java methods used in getLowPowerStats
|
static jmethodID jgetAndUpdatePlatformState = NULL;
|
static jmethodID jgetSubsystem = NULL;
|
static jmethodID jputVoter = NULL;
|
static jmethodID jputState = NULL;
|
|
std::mutex gPowerHalMutex;
|
std::unordered_map<uint32_t, std::string> gPowerStatsHalEntityNames = {};
|
std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>>
|
gPowerStatsHalStateNames = {};
|
std::vector<uint32_t> gPowerStatsHalPlatformIds = {};
|
std::vector<uint32_t> gPowerStatsHalSubsystemIds = {};
|
sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0 = nullptr;
|
std::function<void(JNIEnv*, jobject)> gGetLowPowerStatsImpl = {};
|
std::function<jint(JNIEnv*, jobject)> gGetPlatformLowPowerStatsImpl = {};
|
std::function<jint(JNIEnv*, jobject)> gGetSubsystemLowPowerStatsImpl = {};
|
|
// Cellular/Wifi power monitor rail information
|
static jmethodID jupdateRailData = NULL;
|
static jmethodID jsetRailStatsAvailability = NULL;
|
|
std::function<void(JNIEnv*, jobject)> gGetRailEnergyPowerStatsImpl = {};
|
|
std::unordered_map<uint32_t, std::pair<std::string, std::string>> gPowerStatsHalRailNames = {};
|
static bool power_monitor_available = false;
|
|
// The caller must be holding gPowerHalMutex.
|
static void deinitPowerStatsLocked() {
|
gPowerStatsHalV1_0 = nullptr;
|
}
|
|
struct PowerHalDeathRecipient : virtual public hardware::hidl_death_recipient {
|
virtual void serviceDied(uint64_t cookie,
|
const wp<android::hidl::base::V1_0::IBase>& who) override {
|
// The HAL just died. Reset all handles to HAL services.
|
std::lock_guard<std::mutex> lock(gPowerHalMutex);
|
deinitPowerStatsLocked();
|
}
|
};
|
|
sp<PowerHalDeathRecipient> gDeathRecipient = new PowerHalDeathRecipient();
|
|
class WakeupCallback : public BnSuspendCallback {
|
public:
|
binder::Status notifyWakeup(bool success) override {
|
ALOGI("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
|
int ret = sem_post(&wakeup_sem);
|
if (ret < 0) {
|
char buf[80];
|
strerror_r(errno, buf, sizeof(buf));
|
ALOGE("Error posting wakeup sem: %s\n", buf);
|
}
|
return binder::Status::ok();
|
}
|
};
|
|
static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf)
|
{
|
if (outBuf == NULL) {
|
jniThrowException(env, "java/lang/NullPointerException", "null argument");
|
return -1;
|
}
|
|
// Register our wakeup callback if not yet done.
|
if (!wakeup_init) {
|
wakeup_init = true;
|
ALOGV("Creating semaphore...");
|
int ret = sem_init(&wakeup_sem, 0, 0);
|
if (ret < 0) {
|
char buf[80];
|
strerror_r(errno, buf, sizeof(buf));
|
ALOGE("Error creating semaphore: %s\n", buf);
|
jniThrowException(env, "java/lang/IllegalStateException", buf);
|
return -1;
|
}
|
sp<ISuspendControlService> suspendControl = getSuspendControl();
|
bool isRegistered = false;
|
suspendControl->registerCallback(new WakeupCallback(), &isRegistered);
|
if (!isRegistered) {
|
ALOGE("Failed to register wakeup callback");
|
}
|
}
|
|
// Wait for wakeup.
|
ALOGV("Waiting for wakeup...");
|
// TODO(b/116747600): device can suspend and wakeup after sem_wait() finishes and before wakeup
|
// reason is recorded, i.e. BatteryStats might occasionally miss wakeup events.
|
int ret = sem_wait(&wakeup_sem);
|
if (ret < 0) {
|
char buf[80];
|
strerror_r(errno, buf, sizeof(buf));
|
ALOGE("Error waiting on semaphore: %s\n", buf);
|
// Return 0 here to let it continue looping but not return results.
|
return 0;
|
}
|
|
FILE *fp = fopen(LAST_RESUME_REASON, "r");
|
if (fp == NULL) {
|
ALOGE("Failed to open %s", LAST_RESUME_REASON);
|
return -1;
|
}
|
|
char* mergedreason = (char*)env->GetDirectBufferAddress(outBuf);
|
int remainreasonlen = (int)env->GetDirectBufferCapacity(outBuf);
|
|
ALOGV("Reading wakeup reasons");
|
char* mergedreasonpos = mergedreason;
|
char reasonline[128];
|
int i = 0;
|
while (fgets(reasonline, sizeof(reasonline), fp) != NULL) {
|
char* pos = reasonline;
|
char* endPos;
|
int len;
|
// First field is the index or 'Abort'.
|
int irq = (int)strtol(pos, &endPos, 10);
|
if (pos != endPos) {
|
// Write the irq number to the merged reason string.
|
len = snprintf(mergedreasonpos, remainreasonlen, i == 0 ? "%d" : ":%d", irq);
|
} else {
|
// The first field is not an irq, it may be the word Abort.
|
const size_t abortPrefixLen = strlen("Abort:");
|
if (strncmp(pos, "Abort:", abortPrefixLen) != 0) {
|
// Ooops.
|
ALOGE("Bad reason line: %s", reasonline);
|
continue;
|
}
|
|
// Write 'Abort' to the merged reason string.
|
len = snprintf(mergedreasonpos, remainreasonlen, i == 0 ? "Abort" : ":Abort");
|
endPos = pos + abortPrefixLen;
|
}
|
pos = endPos;
|
|
if (len >= 0 && len < remainreasonlen) {
|
mergedreasonpos += len;
|
remainreasonlen -= len;
|
}
|
|
// Skip whitespace; rest of the buffer is the reason string.
|
while (*pos == ' ') {
|
pos++;
|
}
|
|
// Chop newline at end.
|
char* endpos = pos;
|
while (*endpos != 0) {
|
if (*endpos == '\n') {
|
*endpos = 0;
|
break;
|
}
|
endpos++;
|
}
|
|
len = snprintf(mergedreasonpos, remainreasonlen, ":%s", pos);
|
if (len >= 0 && len < remainreasonlen) {
|
mergedreasonpos += len;
|
remainreasonlen -= len;
|
}
|
i++;
|
}
|
|
ALOGV("Got %d reasons", i);
|
if (i > 0) {
|
*mergedreasonpos = 0;
|
}
|
|
if (fclose(fp) != 0) {
|
ALOGE("Failed to close %s", LAST_RESUME_REASON);
|
return -1;
|
}
|
return mergedreasonpos - mergedreason;
|
}
|
|
// The caller must be holding gPowerHalMutex.
|
static bool checkResultLocked(const Return<void> &ret, const char* function) {
|
if (!ret.isOk()) {
|
ALOGE("%s failed: requested HAL service not available. Description: %s",
|
function, ret.description().c_str());
|
if (ret.isDeadObject()) {
|
deinitPowerStatsLocked();
|
}
|
return false;
|
}
|
return true;
|
}
|
|
// The caller must be holding gPowerHalMutex.
|
// gPowerStatsHalV1_0 must not be null
|
static bool initializePowerStats() {
|
using android::hardware::power::stats::V1_0::Status;
|
using android::hardware::power::stats::V1_0::PowerEntityType;
|
|
// Clear out previous content if we are re-initializing
|
gPowerStatsHalEntityNames.clear();
|
gPowerStatsHalStateNames.clear();
|
gPowerStatsHalPlatformIds.clear();
|
gPowerStatsHalSubsystemIds.clear();
|
gPowerStatsHalRailNames.clear();
|
|
Return<void> ret;
|
ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) {
|
if (status != Status::SUCCESS) {
|
ALOGE("Error getting power entity info");
|
return;
|
}
|
|
// construct lookup table of powerEntityId to power entity name
|
// also construct vector of platform and subsystem IDs
|
for (auto info : infos) {
|
gPowerStatsHalEntityNames.emplace(info.powerEntityId, info.powerEntityName);
|
if (info.type == PowerEntityType::POWER_DOMAIN) {
|
gPowerStatsHalPlatformIds.emplace_back(info.powerEntityId);
|
} else {
|
gPowerStatsHalSubsystemIds.emplace_back(info.powerEntityId);
|
}
|
}
|
});
|
if (!checkResultLocked(ret, __func__)) {
|
return false;
|
}
|
|
ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) {
|
if (status != Status::SUCCESS) {
|
ALOGE("Error getting state info");
|
return;
|
}
|
|
// construct lookup table of powerEntityId, powerEntityStateId to power entity state name
|
for (auto stateSpace : stateSpaces) {
|
std::unordered_map<uint32_t, std::string> stateNames = {};
|
for (auto state : stateSpace.states) {
|
stateNames.emplace(state.powerEntityStateId,
|
state.powerEntityStateName);
|
}
|
gPowerStatsHalStateNames.emplace(stateSpace.powerEntityId, stateNames);
|
}
|
});
|
if (!checkResultLocked(ret, __func__)) {
|
return false;
|
}
|
|
// Get Power monitor rails available
|
ret = gPowerStatsHalV1_0->getRailInfo([](auto rails, auto status) {
|
if (status != Status::SUCCESS) {
|
ALOGW("Rail information is not available");
|
power_monitor_available = false;
|
return;
|
}
|
|
// Fill out rail names/subsystems into gPowerStatsHalRailNames
|
for (auto rail : rails) {
|
gPowerStatsHalRailNames.emplace(rail.index,
|
std::make_pair(rail.railName, rail.subsysName));
|
}
|
if (!gPowerStatsHalRailNames.empty()) {
|
power_monitor_available = true;
|
}
|
});
|
if (!checkResultLocked(ret, __func__)) {
|
return false;
|
}
|
|
return (!gPowerStatsHalEntityNames.empty()) && (!gPowerStatsHalStateNames.empty());
|
}
|
|
// The caller must be holding gPowerHalMutex.
|
static bool getPowerStatsHalLocked() {
|
if (gPowerStatsHalV1_0 == nullptr) {
|
gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService();
|
if (gPowerStatsHalV1_0 == nullptr) {
|
ALOGE("Unable to get power.stats HAL service.");
|
return false;
|
}
|
|
// Link death recipient to power.stats service handle
|
hardware::Return<bool> linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0);
|
if (!linked.isOk()) {
|
ALOGE("Transaction error in linking to power.stats HAL death: %s",
|
linked.description().c_str());
|
deinitPowerStatsLocked();
|
return false;
|
} else if (!linked) {
|
ALOGW("Unable to link to power.stats HAL death notifications");
|
// We should still continue even though linking failed
|
}
|
return initializePowerStats();
|
}
|
return true;
|
}
|
|
// The caller must be holding powerHalMutex.
|
static void getPowerStatsHalLowPowerData(JNIEnv* env, jobject jrpmStats) {
|
using android::hardware::power::stats::V1_0::Status;
|
|
if (!getPowerStatsHalLocked()) {
|
ALOGE("failed to get low power stats");
|
return;
|
}
|
|
// Get power entity state residency data
|
bool success = false;
|
Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({},
|
[&env, &jrpmStats, &success](auto results, auto status) {
|
if (status == Status::NOT_SUPPORTED) {
|
ALOGW("getPowerEntityStateResidencyData is not supported");
|
success = false;
|
return;
|
}
|
|
for (auto result : results) {
|
jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem,
|
env->NewStringUTF(gPowerStatsHalEntityNames.at(result.powerEntityId).c_str()));
|
if (jsubsystem == NULL) {
|
ALOGE("The rpmstats jni jobject jsubsystem is null.");
|
return;
|
}
|
for (auto stateResidency : result.stateResidencyData) {
|
|
env->CallVoidMethod(jsubsystem, jputState,
|
env->NewStringUTF(gPowerStatsHalStateNames.at(result.powerEntityId)
|
.at(stateResidency.powerEntityStateId).c_str()),
|
stateResidency.totalTimeInStateMs,
|
stateResidency.totalStateEntryCount);
|
}
|
}
|
success = true;
|
});
|
checkResultLocked(ret, __func__);
|
if (!success) {
|
ALOGE("getPowerEntityStateResidencyData failed");
|
}
|
}
|
|
static jint getPowerStatsHalPlatformData(JNIEnv* env, jobject outBuf) {
|
using android::hardware::power::stats::V1_0::Status;
|
using hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
|
using hardware::power::stats::V1_0::PowerEntityStateResidencyData;
|
|
if (!getPowerStatsHalLocked()) {
|
ALOGE("failed to get low power stats");
|
return -1;
|
}
|
|
char *output = (char*)env->GetDirectBufferAddress(outBuf);
|
char *offset = output;
|
int remaining = (int)env->GetDirectBufferCapacity(outBuf);
|
int total_added = -1;
|
|
// Get power entity state residency data
|
Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
|
gPowerStatsHalPlatformIds,
|
[&offset, &remaining, &total_added](auto results, auto status) {
|
if (status == Status::NOT_SUPPORTED) {
|
ALOGW("getPowerEntityStateResidencyData is not supported");
|
return;
|
}
|
|
for (size_t i = 0; i < results.size(); i++) {
|
const PowerEntityStateResidencyResult& result = results[i];
|
|
for (size_t j = 0; j < result.stateResidencyData.size(); j++) {
|
const PowerEntityStateResidencyData& stateResidency =
|
result.stateResidencyData[j];
|
int added = snprintf(offset, remaining,
|
"state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
|
j + 1, gPowerStatsHalStateNames.at(result.powerEntityId)
|
.at(stateResidency.powerEntityStateId).c_str(),
|
stateResidency.totalTimeInStateMs,
|
stateResidency.totalStateEntryCount);
|
if (added < 0) {
|
break;
|
}
|
if (added > remaining) {
|
added = remaining;
|
}
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
}
|
if (remaining <= 0) {
|
/* rewrite NULL character*/
|
offset--;
|
total_added--;
|
ALOGE("power.stats Hal: buffer not enough");
|
break;
|
}
|
}
|
});
|
if (!checkResultLocked(ret, __func__)) {
|
return -1;
|
}
|
|
total_added += 1;
|
return total_added;
|
}
|
|
static jint getPowerStatsHalSubsystemData(JNIEnv* env, jobject outBuf) {
|
using android::hardware::power::stats::V1_0::Status;
|
using hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
|
using hardware::power::stats::V1_0::PowerEntityStateResidencyData;
|
|
if (!getPowerStatsHalLocked()) {
|
ALOGE("failed to get low power stats");
|
return -1;
|
}
|
|
char *output = (char*)env->GetDirectBufferAddress(outBuf);
|
char *offset = output;
|
int remaining = (int)env->GetDirectBufferCapacity(outBuf);
|
int total_added = -1;
|
|
// Get power entity state residency data
|
Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
|
gPowerStatsHalSubsystemIds,
|
[&offset, &remaining, &total_added](auto results, auto status) {
|
if (status == Status::NOT_SUPPORTED) {
|
ALOGW("getPowerEntityStateResidencyData is not supported");
|
return;
|
}
|
|
int added = snprintf(offset, remaining, "SubsystemPowerState ");
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
|
for (size_t i = 0; i < results.size(); i++) {
|
const PowerEntityStateResidencyResult& result = results[i];
|
added = snprintf(offset, remaining, "subsystem_%zu name=%s ",
|
i + 1, gPowerStatsHalEntityNames.at(result.powerEntityId).c_str());
|
if (added < 0) {
|
break;
|
}
|
|
if (added > remaining) {
|
added = remaining;
|
}
|
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
|
for (size_t j = 0; j < result.stateResidencyData.size(); j++) {
|
const PowerEntityStateResidencyData& stateResidency =
|
result.stateResidencyData[j];
|
added = snprintf(offset, remaining,
|
"state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%"
|
PRIu64 " ", j + 1, gPowerStatsHalStateNames.at(result.powerEntityId)
|
.at(stateResidency.powerEntityStateId).c_str(),
|
stateResidency.totalTimeInStateMs,
|
stateResidency.totalStateEntryCount,
|
stateResidency.lastEntryTimestampMs);
|
if (added < 0) {
|
break;
|
}
|
if (added > remaining) {
|
added = remaining;
|
}
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
}
|
if (remaining <= 0) {
|
/* rewrite NULL character*/
|
offset--;
|
total_added--;
|
ALOGE("power.stats Hal: buffer not enough");
|
break;
|
}
|
}
|
});
|
if (!checkResultLocked(ret, __func__)) {
|
return -1;
|
}
|
|
total_added += 1;
|
return total_added;
|
}
|
|
static void getPowerStatsHalRailEnergyData(JNIEnv* env, jobject jrailStats) {
|
using android::hardware::power::stats::V1_0::Status;
|
using android::hardware::power::stats::V1_0::EnergyData;
|
|
if (!getPowerStatsHalLocked()) {
|
ALOGE("failed to get power stats");
|
return;
|
}
|
|
if (!power_monitor_available) {
|
env->CallVoidMethod(jrailStats, jsetRailStatsAvailability, false);
|
ALOGW("Rail energy data is not available");
|
return;
|
}
|
|
// Get power rail energySinceBoot data
|
Return<void> ret = gPowerStatsHalV1_0->getEnergyData({},
|
[&env, &jrailStats](auto energyData, auto status) {
|
if (status == Status::NOT_SUPPORTED) {
|
ALOGW("getEnergyData is not supported");
|
return;
|
}
|
|
for (auto data : energyData) {
|
if (!(data.timestamp > LLONG_MAX || data.energy > LLONG_MAX)) {
|
env->CallVoidMethod(jrailStats,
|
jupdateRailData,
|
data.index,
|
env->NewStringUTF(
|
gPowerStatsHalRailNames.at(data.index).first.c_str()),
|
env->NewStringUTF(
|
gPowerStatsHalRailNames.at(data.index).second.c_str()),
|
data.timestamp,
|
data.energy);
|
} else {
|
ALOGE("Java long overflow seen. Rail index %d not updated", data.index);
|
}
|
}
|
});
|
if (!checkResultLocked(ret, __func__)) {
|
ALOGE("getEnergyData failed");
|
}
|
}
|
|
// The caller must be holding powerHalMutex.
|
static void getPowerHalLowPowerData(JNIEnv* env, jobject jrpmStats) {
|
sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
|
if (powerHalV1_0 == nullptr) {
|
ALOGE("Power Hal not loaded");
|
return;
|
}
|
|
Return<void> ret = powerHalV1_0->getPlatformLowPowerStats(
|
[&env, &jrpmStats](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
|
|
if (status != Status::SUCCESS) return;
|
|
for (size_t i = 0; i < states.size(); i++) {
|
const PowerStatePlatformSleepState& state = states[i];
|
|
jobject jplatformState = env->CallObjectMethod(jrpmStats,
|
jgetAndUpdatePlatformState,
|
env->NewStringUTF(state.name.c_str()),
|
state.residencyInMsecSinceBoot,
|
state.totalTransitions);
|
if (jplatformState == NULL) {
|
ALOGE("The rpmstats jni jobject jplatformState is null.");
|
return;
|
}
|
|
for (size_t j = 0; j < state.voters.size(); j++) {
|
const PowerStateVoter& voter = state.voters[j];
|
env->CallVoidMethod(jplatformState, jputVoter,
|
env->NewStringUTF(voter.name.c_str()),
|
voter.totalTimeInMsecVotedForSinceBoot,
|
voter.totalNumberOfTimesVotedSinceBoot);
|
}
|
}
|
});
|
if (!processPowerHalReturn(ret, "getPlatformLowPowerStats")) {
|
return;
|
}
|
|
// Trying to get IPower 1.1, this will succeed only for devices supporting 1.1
|
sp<IPowerV1_1> powerHal_1_1 = getPowerHalV1_1();
|
if (powerHal_1_1 == nullptr) {
|
// This device does not support IPower@1.1, exiting gracefully
|
return;
|
}
|
ret = powerHal_1_1->getSubsystemLowPowerStats(
|
[&env, &jrpmStats](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
|
|
if (status != Status::SUCCESS) return;
|
|
if (subsystems.size() > 0) {
|
for (size_t i = 0; i < subsystems.size(); i++) {
|
const PowerStateSubsystem &subsystem = subsystems[i];
|
|
jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem,
|
env->NewStringUTF(subsystem.name.c_str()));
|
if (jsubsystem == NULL) {
|
ALOGE("The rpmstats jni jobject jsubsystem is null.");
|
return;
|
}
|
|
for (size_t j = 0; j < subsystem.states.size(); j++) {
|
const PowerStateSubsystemSleepState& state = subsystem.states[j];
|
env->CallVoidMethod(jsubsystem, jputState,
|
env->NewStringUTF(state.name.c_str()),
|
state.residencyInMsecSinceBoot,
|
state.totalTransitions);
|
}
|
}
|
}
|
});
|
processPowerHalReturn(ret, "getSubsystemLowPowerStats");
|
}
|
|
static jint getPowerHalPlatformData(JNIEnv* env, jobject outBuf) {
|
char *output = (char*)env->GetDirectBufferAddress(outBuf);
|
char *offset = output;
|
int remaining = (int)env->GetDirectBufferCapacity(outBuf);
|
int total_added = -1;
|
|
{
|
sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
|
if (powerHalV1_0 == nullptr) {
|
ALOGE("Power Hal not loaded");
|
return -1;
|
}
|
|
Return<void> ret = powerHalV1_0->getPlatformLowPowerStats(
|
[&offset, &remaining, &total_added](hidl_vec<PowerStatePlatformSleepState> states,
|
Status status) {
|
if (status != Status::SUCCESS)
|
return;
|
for (size_t i = 0; i < states.size(); i++) {
|
int added;
|
const PowerStatePlatformSleepState& state = states[i];
|
|
added = snprintf(offset, remaining,
|
"state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
|
i + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
|
state.totalTransitions);
|
if (added < 0) {
|
break;
|
}
|
if (added > remaining) {
|
added = remaining;
|
}
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
|
for (size_t j = 0; j < state.voters.size(); j++) {
|
const PowerStateVoter& voter = state.voters[j];
|
added = snprintf(offset, remaining,
|
"voter_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
|
j + 1, voter.name.c_str(),
|
voter.totalTimeInMsecVotedForSinceBoot,
|
voter.totalNumberOfTimesVotedSinceBoot);
|
if (added < 0) {
|
break;
|
}
|
if (added > remaining) {
|
added = remaining;
|
}
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
}
|
|
if (remaining <= 0) {
|
/* rewrite NULL character*/
|
offset--;
|
total_added--;
|
ALOGE("PowerHal: buffer not enough");
|
break;
|
}
|
}
|
}
|
);
|
|
if (!processPowerHalReturn(ret, "getPlatformLowPowerStats")) {
|
return -1;
|
}
|
}
|
*offset = 0;
|
total_added += 1;
|
return total_added;
|
}
|
|
static jint getPowerHalSubsystemData(JNIEnv* env, jobject outBuf) {
|
char *output = (char*)env->GetDirectBufferAddress(outBuf);
|
char *offset = output;
|
int remaining = (int)env->GetDirectBufferCapacity(outBuf);
|
int total_added = -1;
|
|
// This is a IPower 1.1 API
|
sp<IPowerV1_1> powerHal_1_1 = nullptr;
|
|
{
|
// Trying to get 1.1, this will succeed only for devices supporting 1.1
|
powerHal_1_1 = getPowerHalV1_1();
|
if (powerHal_1_1 == nullptr) {
|
//This device does not support IPower@1.1, exiting gracefully
|
return 0;
|
}
|
|
Return<void> ret = powerHal_1_1->getSubsystemLowPowerStats(
|
[&offset, &remaining, &total_added](hidl_vec<PowerStateSubsystem> subsystems,
|
Status status) {
|
|
if (status != Status::SUCCESS)
|
return;
|
|
if (subsystems.size() > 0) {
|
int added = snprintf(offset, remaining, "SubsystemPowerState ");
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
|
for (size_t i = 0; i < subsystems.size(); i++) {
|
const PowerStateSubsystem &subsystem = subsystems[i];
|
|
added = snprintf(offset, remaining,
|
"subsystem_%zu name=%s ", i + 1, subsystem.name.c_str());
|
if (added < 0) {
|
break;
|
}
|
|
if (added > remaining) {
|
added = remaining;
|
}
|
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
|
for (size_t j = 0; j < subsystem.states.size(); j++) {
|
const PowerStateSubsystemSleepState& state = subsystem.states[j];
|
added = snprintf(offset, remaining,
|
"state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%" PRIu64 " ",
|
j + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
|
state.totalTransitions, state.lastEntryTimestampMs);
|
if (added < 0) {
|
break;
|
}
|
|
if (added > remaining) {
|
added = remaining;
|
}
|
|
offset += added;
|
remaining -= added;
|
total_added += added;
|
}
|
|
if (remaining <= 0) {
|
/* rewrite NULL character*/
|
offset--;
|
total_added--;
|
ALOGE("PowerHal: buffer not enough");
|
break;
|
}
|
}
|
}
|
}
|
);
|
|
if (!processPowerHalReturn(ret, "getSubsystemLowPowerStats")) {
|
return -1;
|
}
|
}
|
|
*offset = 0;
|
total_added += 1;
|
return total_added;
|
}
|
|
static void setUpPowerStatsLocked() {
|
// First see if power.stats HAL is available. Fall back to power HAL if
|
// power.stats HAL is unavailable.
|
if (android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) {
|
ALOGI("Using power.stats HAL");
|
gGetLowPowerStatsImpl = getPowerStatsHalLowPowerData;
|
gGetPlatformLowPowerStatsImpl = getPowerStatsHalPlatformData;
|
gGetSubsystemLowPowerStatsImpl = getPowerStatsHalSubsystemData;
|
gGetRailEnergyPowerStatsImpl = getPowerStatsHalRailEnergyData;
|
} else if (android::hardware::power::V1_0::IPower::getService() != nullptr) {
|
ALOGI("Using power HAL");
|
gGetLowPowerStatsImpl = getPowerHalLowPowerData;
|
gGetPlatformLowPowerStatsImpl = getPowerHalPlatformData;
|
gGetSubsystemLowPowerStatsImpl = getPowerHalSubsystemData;
|
gGetRailEnergyPowerStatsImpl = NULL;
|
}
|
}
|
|
static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats) {
|
if (jrpmStats == NULL) {
|
jniThrowException(env, "java/lang/NullPointerException",
|
"The rpmstats jni input jobject jrpmStats is null.");
|
return;
|
}
|
if (jgetAndUpdatePlatformState == NULL || jgetSubsystem == NULL
|
|| jputVoter == NULL || jputState == NULL) {
|
ALOGE("A rpmstats jni jmethodID is null.");
|
return;
|
}
|
|
std::lock_guard<std::mutex> lock(gPowerHalMutex);
|
|
if (!gGetLowPowerStatsImpl) {
|
setUpPowerStatsLocked();
|
}
|
|
if (gGetLowPowerStatsImpl) {
|
return gGetLowPowerStatsImpl(env, jrpmStats);
|
}
|
|
ALOGE("Unable to load Power Hal or power.stats HAL");
|
return;
|
}
|
|
static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
|
if (outBuf == NULL) {
|
jniThrowException(env, "java/lang/NullPointerException", "null argument");
|
return -1;
|
}
|
|
std::lock_guard<std::mutex> lock(gPowerHalMutex);
|
|
if (!gGetPlatformLowPowerStatsImpl) {
|
setUpPowerStatsLocked();
|
}
|
|
if (gGetPlatformLowPowerStatsImpl) {
|
return gGetPlatformLowPowerStatsImpl(env, outBuf);
|
}
|
|
ALOGE("Unable to load Power Hal or power.stats HAL");
|
return -1;
|
}
|
|
static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
|
if (outBuf == NULL) {
|
jniThrowException(env, "java/lang/NullPointerException", "null argument");
|
return -1;
|
}
|
|
std::lock_guard<std::mutex> lock(gPowerHalMutex);
|
|
if (!gGetSubsystemLowPowerStatsImpl) {
|
setUpPowerStatsLocked();
|
}
|
|
if (gGetSubsystemLowPowerStatsImpl) {
|
return gGetSubsystemLowPowerStatsImpl(env, outBuf);
|
}
|
|
ALOGE("Unable to load Power Hal or power.stats HAL");
|
return -1;
|
}
|
|
static void getRailEnergyPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrailStats) {
|
if (jrailStats == NULL) {
|
jniThrowException(env, "java/lang/NullPointerException",
|
"The railstats jni input jobject jrailStats is null.");
|
return;
|
}
|
if (jupdateRailData == NULL) {
|
ALOGE("A railstats jni jmethodID is null.");
|
return;
|
}
|
|
std::lock_guard<std::mutex> lock(gPowerHalMutex);
|
|
if (!gGetRailEnergyPowerStatsImpl) {
|
setUpPowerStatsLocked();
|
}
|
|
if (gGetRailEnergyPowerStatsImpl) {
|
gGetRailEnergyPowerStatsImpl(env, jrailStats);
|
return;
|
}
|
|
if (jsetRailStatsAvailability == NULL) {
|
ALOGE("setRailStatsAvailability jni jmethodID is null.");
|
return;
|
}
|
env->CallVoidMethod(jrailStats, jsetRailStatsAvailability, false);
|
ALOGE("Unable to load Power.Stats.HAL. Setting rail availability to false");
|
return;
|
}
|
|
static const JNINativeMethod method_table[] = {
|
{ "nativeWaitWakeup", "(Ljava/nio/ByteBuffer;)I", (void*)nativeWaitWakeup },
|
{ "getLowPowerStats", "(Lcom/android/internal/os/RpmStats;)V", (void*)getLowPowerStats },
|
{ "getPlatformLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getPlatformLowPowerStats },
|
{ "getSubsystemLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getSubsystemLowPowerStats },
|
{ "getRailEnergyPowerStats", "(Lcom/android/internal/os/RailStats;)V",
|
(void*)getRailEnergyPowerStats },
|
};
|
|
int register_android_server_BatteryStatsService(JNIEnv *env)
|
{
|
// get java classes and methods
|
jclass clsRpmStats = env->FindClass("com/android/internal/os/RpmStats");
|
jclass clsPowerStatePlatformSleepState =
|
env->FindClass("com/android/internal/os/RpmStats$PowerStatePlatformSleepState");
|
jclass clsPowerStateSubsystem =
|
env->FindClass("com/android/internal/os/RpmStats$PowerStateSubsystem");
|
jclass clsRailStats = env->FindClass("com/android/internal/os/RailStats");
|
if (clsRpmStats == NULL || clsPowerStatePlatformSleepState == NULL
|
|| clsPowerStateSubsystem == NULL || clsRailStats == NULL) {
|
ALOGE("A rpmstats jni jclass is null.");
|
} else {
|
jgetAndUpdatePlatformState = env->GetMethodID(clsRpmStats, "getAndUpdatePlatformState",
|
"(Ljava/lang/String;JI)Lcom/android/internal/os/RpmStats$PowerStatePlatformSleepState;");
|
jgetSubsystem = env->GetMethodID(clsRpmStats, "getSubsystem",
|
"(Ljava/lang/String;)Lcom/android/internal/os/RpmStats$PowerStateSubsystem;");
|
jputVoter = env->GetMethodID(clsPowerStatePlatformSleepState, "putVoter",
|
"(Ljava/lang/String;JI)V");
|
jputState = env->GetMethodID(clsPowerStateSubsystem, "putState",
|
"(Ljava/lang/String;JI)V");
|
jupdateRailData = env->GetMethodID(clsRailStats, "updateRailData",
|
"(JLjava/lang/String;Ljava/lang/String;JJ)V");
|
jsetRailStatsAvailability = env->GetMethodID(clsRailStats, "setRailStatsAvailability",
|
"(Z)V");
|
}
|
|
return jniRegisterNativeMethods(env, "com/android/server/am/BatteryStatsService",
|
method_table, NELEM(method_table));
|
}
|
|
};
|