* Copyright (c) 2022-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "decision_maker.h"
#include <climits>
#include "bg_transient_task_mgr.h"
#include "bgtask_common.h"
#include "transient_task_log.h"
#include "time_provider.h"
#include "iservice_registry.h"
#include "system_ability_definition.h"
#include "hisysevent.h"
#include "data_storage_helper.h"
#include "bgtask_config.h"
#include "app_mgr_helper.h"
using namespace std;
namespace OHOS {
namespace BackgroundTaskMgr {
namespace {
const std::string SUSPEND_MANAGER_CONFIG_FILE = "/etc/efficiency_manager/suspend_manager_config.json";
}
DecisionMaker::DecisionMaker(const shared_ptr<TimerManager>& timerManager, const shared_ptr<DeviceInfoManager>& device)
{
lock_guard<mutex> lock(lock_);
timerManager_ = timerManager;
deviceInfoManager_ = device;
}
DecisionMaker::~DecisionMaker() {}
void DecisionMaker::OnProcessStateChanged(const AppExecFwk::ProcessData &processData)
{
bool isForeground = processData.state == AppExecFwk::AppProcessState::APP_STATE_FOREGROUND ||
processData.state == AppExecFwk::AppProcessState::APP_STATE_FOCUS;
bool isBackground = processData.state == AppExecFwk::AppProcessState::APP_STATE_BACKGROUND;
BGTASK_LOGI("pid: %{public}d, state: %{public}d", processData.pid, processData.state);
UpdateForegroundUidPidMap(processData.uid, processData.pid, isForeground);
if (isForeground || isBackground) {
HandleStateChange(processData.bundleName, processData.uid, isForeground, isBackground);
}
}
void DecisionMaker::OnProcessDied(const AppExecFwk::ProcessData &processData)
{
UpdateForegroundUidPidMap(processData.uid, processData.pid, false);
if (TryStartAccounting(processData.uid, processData.bundleName) == ERR_OK) {
BGTASK_LOGI("%{public}s_%{public}d start accounting because pid:%{public}d died",
processData.bundleName.c_str(), processData.uid, processData.pid);
}
}
ErrCode DecisionMaker::TryStartAccounting(int32_t uid, const std::string &bundleName)
{
lock_guard<mutex> lock(lock_);
auto key = std::make_shared<KeyInfo>(bundleName, uid);
auto it = pkgDelaySuspendInfoMap_.find(key);
if (it == pkgDelaySuspendInfoMap_.end()) {
BGTASK_LOGD("pkgname: %{public}s, uid: %{public}d not request transient task.", bundleName.c_str(), uid);
return ERR_BGTASK_NOREQUEST_TASK;
}
if (CanStartAccountingLocked(it->second)) {
BGTASK_LOGD("pkgname: %{public}s, uid: %{public}d start accounting", bundleName.c_str(), uid);
it->second->StartAccounting();
return ERR_OK;
} else {
BGTASK_LOGD("pkgname: %{public}s, uid: %{public}d can't start accounting", bundleName.c_str(), uid);
return ERR_BGTASK_FOREGROUND;
}
}
void DecisionMaker::UpdateForegroundUidPidMap(int32_t uid, int32_t pid, bool isForeground)
{
lock_guard<recursive_mutex> lock(recMutex_);
set<int32_t>& pids = foregroundUidPidMap_[uid];
if (isForeground) {
pids.insert(pid);
} else {
pids.erase(pid);
if (pids.empty()) {
foregroundUidPidMap_.erase(uid);
}
}
}
bool DecisionMaker::IsUidForeground(int32_t uid)
{
lock_guard<recursive_mutex> lock(recMutex_);
return foregroundUidPidMap_.count(uid) != 0;
}
void DecisionMaker::HandleStateChange(
const std::string &bundleName, int32_t uid, bool isForeground, bool isBackground)
{
lock_guard<mutex> lock(lock_);
auto key = std::make_shared<KeyInfo>(bundleName, uid);
if (isForeground) {
auto it = pkgDelaySuspendInfoMap_.find(key);
if (it != pkgDelaySuspendInfoMap_.end()) {
auto pkgInfo = it->second;
BGTASK_LOGD("pkgname: %{public}s, uid: %{public}d is foreground, stop accounting",
bundleName.c_str(), uid);
pkgInfo->StopAccountingAll();
}
auto itBg = pkgBgDurationMap_.find(key);
if (itBg != pkgBgDurationMap_.end()) {
pkgBgDurationMap_.erase(itBg);
}
} else if (isBackground) {
auto it = pkgDelaySuspendInfoMap_.find(key);
if (it == pkgDelaySuspendInfoMap_.end()) {
BGTASK_LOGD("pkgname: %{public}s, uid: %{public}d is not in delay suspend list",
bundleName.c_str(), uid);
return;
}
auto pkgInfo = it->second;
if (CanStartAccountingLocked(pkgInfo)) {
BGTASK_LOGD("pkgname: %{public}s, uid: %{public}d is background, start accounting",
bundleName.c_str(), uid);
pkgInfo->StartAccounting();
pkgBgDurationMap_[key] = TimeProvider::GetCurrentTime();
}
}
}
int DecisionMaker::GetAllowRequestTime()
{
static int time = 0;
if (time != 0) {
return time;
}
if (!DelayedSingleton<DataStorageHelper>::GetInstance()->ParseFastSuspendDozeTime(
SUSPEND_MANAGER_CONFIG_FILE, time)) {
time = ALLOW_REQUEST_TIME_BG;
}
BGTASK_LOGI("time = %{public}d", time);
return time;
}
ErrCode DecisionMaker::CheckQuotaTime(const std::shared_ptr<PkgDelaySuspendInfo>& pkgInfo, const std::string &name,
int32_t uid, const std::shared_ptr<KeyInfo>& key, bool &needSetTime)
{
ErrCode ret = pkgInfo->IsAllowRequest();
if (ret == ERR_BGTASK_TIME_INSUFFICIENT) {
bool isExemptedApp = DelayedSingleton<BgtaskConfig>::GetInstance()->
IsTransientTaskExemptedQuatoApp(name);
BGTASK_LOGI("pkgname %{public}s has no quota time, isExemptedApp %{public}d", name.c_str(), isExemptedApp);
if (isExemptedApp) {
needSetTime = true;
return ERR_OK;
} else {
return ERR_BGTASK_TIME_INSUFFICIENT;
}
}
if (ret != ERR_OK) {
BGTASK_LOGE("Request not allow by its info");
return ret;
}
return ERR_OK;
}
ErrCode DecisionMaker::Decide(const std::shared_ptr<KeyInfo>& key, const std::shared_ptr<DelaySuspendInfoEx>& delayInfo)
{
lock_guard<mutex> lock(lock_);
if (key == nullptr || delayInfo == nullptr) {
BGTASK_LOGE("Invalid key or delayInfo");
return ERR_BGTASK_NO_MEMORY;
}
ResetDayQuotaLocked();
auto findBgDurationIt = pkgBgDurationMap_.find(key);
if (findBgDurationIt != pkgBgDurationMap_.end()) {
if (TimeProvider::GetCurrentTime() - findBgDurationIt->second > GetAllowRequestTime()) {
BGTASK_LOGI("Request not allow after entering background for a valid duration, %{public}s",
key->ToString().c_str());
return ERR_BGTASK_NOT_IN_PRESET_TIME;
}
}
const string &name = key->GetPkg();
int32_t uid = key->GetUid();
auto findInfoIt = pkgDelaySuspendInfoMap_.find(key);
if (findInfoIt == pkgDelaySuspendInfoMap_.end()) {
pkgDelaySuspendInfoMap_[key] = make_shared<PkgDelaySuspendInfo>(name, uid, timerManager_);
}
auto pkgInfo = pkgDelaySuspendInfoMap_[key];
bool needSetTime = false;
ErrCode ret = CheckQuotaTime(pkgInfo, name, uid, key, needSetTime);
if (ret != ERR_OK) {
return ret;
}
delayInfo->SetRequestId(NewDelaySuspendRequestId());
pkgInfo->AddRequest(delayInfo, GetDelayTime(), needSetTime);
auto appInfo = make_shared<TransientTaskAppInfo>(name, uid, key->GetPid());
DelayedSingleton<BgTransientTaskMgr>::GetInstance()
->HandleTransientTaskSuscriberTask(appInfo, TransientTaskEventType::TASK_START);
if (pkgInfo->GetRequestSize() == 1) {
suspendController_.RequestSuspendDelay(key);
auto info = make_shared<TransientTaskAppInfo>(name, uid);
DelayedSingleton<BgTransientTaskMgr>::GetInstance()
->HandleTransientTaskSuscriberTask(info, TransientTaskEventType::APP_TASK_START);
}
if (CanStartAccountingLocked(pkgInfo)) {
BGTASK_LOGI("keyinfo: %{public}s request transient task, start account.", key->ToString().c_str());
pkgInfo->StartAccounting(delayInfo->GetRequestId());
}
HiSysEventWrite(HiviewDFX::HiSysEvent::Domain::BACKGROUND_TASK, "TRANSIENT_TASK_APPLY",
HiviewDFX::HiSysEvent::EventType::STATISTIC, "APP_UID", key->GetUid(), "APP_PID", key->GetPid(),
"APP_NAME", key->GetPkg(), "TASKID", delayInfo->GetRequestId(), "VALUE", delayInfo->GetActualDelayTime());
return ERR_OK;
}
ErrCode DecisionMaker::PauseTransientTaskTimeForInner(int32_t uid, const std::string &name)
{
if (IsUidForeground(uid)) {
BGTASK_LOGE("pkgname: %{public}s, uid: %{public}d is foreground applicaion.", name.c_str(), uid);
return ERR_BGTASK_FOREGROUND;
}
lock_guard<mutex> lock(lock_);
auto key = std::make_shared<KeyInfo>(name, uid);
auto it = pkgDelaySuspendInfoMap_.find(key);
if (it == pkgDelaySuspendInfoMap_.end()) {
BGTASK_LOGE("pkgname: %{public}s, uid: %{public}d not request transient task.", name.c_str(), uid);
return ERR_BGTASK_NOREQUEST_TASK;
}
auto pkgInfo = it->second;
pkgInfo->StopAccountingAll();
return ERR_OK;
}
ErrCode DecisionMaker::StartTransientTaskTimeForInner(int32_t uid, const std::string &name)
{
if (IsUidForeground(uid)) {
BGTASK_LOGE("pkgname: %{public}s, uid: %{public}d is foreground applicaion.", name.c_str(), uid);
return ERR_BGTASK_FOREGROUND;
}
ErrCode ret = TryStartAccounting(uid, name);
if (ret != ERR_OK) {
BGTASK_LOGE("pkgname:%{public}s, uid:%{public}d start accounting fail:%{public}d", name.c_str(), uid, ret);
return ret;
}
return ERR_OK;
}
void DecisionMaker::RemoveRequest(const std::shared_ptr<KeyInfo>& key, const int32_t requestId)
{
lock_guard<mutex> lock(lock_);
if (key == nullptr) {
BGTASK_LOGE("Invalid key");
return;
}
auto findInfoIt = pkgDelaySuspendInfoMap_.find(key);
if (findInfoIt != pkgDelaySuspendInfoMap_.end()) {
auto pkgInfo = findInfoIt->second;
pkgInfo->RemoveRequest(requestId);
auto appInfo = make_shared<TransientTaskAppInfo>(key->GetPkg(), key->GetUid(), key->GetPid());
DelayedSingleton<BgTransientTaskMgr>::GetInstance()
->HandleTransientTaskSuscriberTask(appInfo, TransientTaskEventType::TASK_END);
if (pkgInfo->IsRequestEmpty()) {
suspendController_.CancelSuspendDelay(key);
auto info = make_shared<TransientTaskAppInfo>(key->GetPkg(), key->GetUid());
DelayedSingleton<BgTransientTaskMgr>::GetInstance()
->HandleTransientTaskSuscriberTask(info, TransientTaskEventType::APP_TASK_END);
}
BGTASK_LOGI("Remove requestId: %{public}d", requestId);
HiSysEventWrite(HiviewDFX::HiSysEvent::Domain::BACKGROUND_TASK, "TRANSIENT_TASK_CANCEL",
HiviewDFX::HiSysEvent::EventType::STATISTIC, "APP_UID", key->GetUid(), "APP_PID", key->GetPid(),
"APP_NAME", key->GetPkg(), "TASKID", requestId);
}
}
int32_t DecisionMaker::GetRemainingDelayTime(const std::shared_ptr<KeyInfo>& key, const int32_t requestId)
{
lock_guard<mutex> lock(lock_);
if (key == nullptr) {
BGTASK_LOGE("GetRemainingDelayTime, key is null.");
return -1;
}
auto it = pkgDelaySuspendInfoMap_.find(key);
if (it != pkgDelaySuspendInfoMap_.end()) {
auto pkgInfo = it->second;
return pkgInfo->GetRemainDelayTime(requestId);
}
return -1;
}
vector<int32_t> DecisionMaker::GetRequestIdListByKey(const std::shared_ptr<KeyInfo>& key)
{
lock_guard<mutex> lock(lock_);
vector<int32_t> requestIdList;
if (key == nullptr) {
BGTASK_LOGE("GetRequestListByKey, key is null.");
return requestIdList;
}
auto it = pkgDelaySuspendInfoMap_.find(key);
if (it != pkgDelaySuspendInfoMap_.end()) {
auto pkgInfo = it->second;
for (const auto &task : pkgInfo->GetRequestList()) {
requestIdList.emplace_back(task->GetRequestId());
}
} else {
BGTASK_LOGD("pkgname: %{public}s, uid: %{public}d not request transient task.",
key->GetPkg().c_str(), key->GetUid());
}
return requestIdList;
}
int32_t DecisionMaker::GetQuota(const std::shared_ptr<KeyInfo>& key)
{
lock_guard<mutex> lock(lock_);
if (key == nullptr) {
BGTASK_LOGE("GetQuota, key is null.");
return -1;
}
auto it = pkgDelaySuspendInfoMap_.find(key);
if (it != pkgDelaySuspendInfoMap_.end()) {
auto pkgInfo = it->second;
pkgInfo->UpdateQuota();
return pkgInfo->GetQuota();
}
return INIT_QUOTA;
}
bool DecisionMaker::IsFrontApp(const string& pkgName, int32_t uid)
{
lock_guard<mutex> lock(lock_);
vector<AppExecFwk::AppStateData> fgAppList;
if (!AppMgrHelper::GetInstance()->GetForegroundApplications(fgAppList)) {
BGTASK_LOGE("GetForegroundApplications failed");
return false;
}
for (auto fgApp : fgAppList) {
if (fgApp.bundleName == pkgName && fgApp.uid == uid) {
return true;
}
}
return false;
}
bool DecisionMaker::CanStartAccountingLocked(const std::shared_ptr<PkgDelaySuspendInfo>& pkgInfo)
{
int32_t uid = pkgInfo->GetUid();
if (!deviceInfoManager_->IsScreenOn()) {
std::string bundleName = pkgInfo->GetPkg();
BGTASK_LOGI("device is screen off, uid: %{public}d, bunleName: %{public}s start account.",
uid, bundleName.c_str());
return true;
}
return !IsUidForeground(uid);
}
int32_t DecisionMaker::GetDelayTime()
{
return deviceInfoManager_->IsLowPowerMode() ? DELAY_TIME_LOW_POWER : DELAY_TIME_NORMAL;
}
int32_t DecisionMaker::NewDelaySuspendRequestId()
{
if (requestId_ == INT_MAX) {
requestId_ = initRequestId_;
}
return requestId_++;
}
void DecisionMaker::ResetDayQuotaLocked()
{
int64_t currentTime = TimeProvider::GetCurrentTime();
if (!IsAfterOneDay(lastRequestTime_, currentTime)) {
return;
}
for (auto iter = pkgDelaySuspendInfoMap_.begin(); iter != pkgDelaySuspendInfoMap_.end();) {
auto pkgInfo = iter->second;
if (pkgInfo->IsRequestEmpty()) {
iter = pkgDelaySuspendInfoMap_.erase(iter);
} else {
pkgInfo->UpdateQuota(true);
iter++;
}
}
lastRequestTime_ = currentTime;
}
bool DecisionMaker::IsAfterOneDay(int64_t lastRequestTime, int64_t currentTime)
{
if (currentTime - lastRequestTime > QUOTA_UPDATE) {
return true;
}
return false;
}
void DecisionMaker::OnInputEvent(const EventInfo& eventInfo)
{
if (!eventInfo.GetEventId()) {
return;
}
switch (eventInfo.GetEventId()) {
case EVENT_SCREEN_ON:
HandleScreenOn();
break;
case EVENT_SCREEN_OFF:
HandleScreenOff();
break;
default:
break;
}
}
void DecisionMaker::HandleScreenOn()
{
lock_guard<mutex> lock(lock_);
vector<AppExecFwk::AppStateData> fgAppList;
if (!AppMgrHelper::GetInstance()->GetForegroundApplications(fgAppList)) {
BGTASK_LOGE("GetForegroundApplications failed");
return;
}
for (auto fgApp : fgAppList) {
auto key = std::make_shared<KeyInfo>(fgApp.bundleName, fgApp.uid);
auto it = pkgDelaySuspendInfoMap_.find(key);
if (it != pkgDelaySuspendInfoMap_.end()) {
auto pkgInfo = it->second;
BGTASK_LOGI("screen is on and uid: %{public}d is foreground app, stop accounting", fgApp.uid);
pkgInfo->StopAccountingAll();
}
}
}
void DecisionMaker::HandleScreenOff()
{
lock_guard<mutex> lock(lock_);
std::set<int32_t> &transientPauseUid = DelayedSingleton<BgTransientTaskMgr>::GetInstance()
->GetTransientPauseUid();
for (const auto &p : pkgDelaySuspendInfoMap_) {
auto pkgInfo = p.second;
auto findUid = [&pkgInfo](const auto &target) {
return pkgInfo->GetUid() == target;
};
auto findUidIter = find_if(transientPauseUid.begin(), transientPauseUid.end(), findUid);
if (findUidIter != transientPauseUid.end()) {
BGTASK_LOGI("uid: %{public}d transient task freeze, not can start.", pkgInfo->GetUid());
continue;
}
if (CanStartAccountingLocked(pkgInfo)) {
BGTASK_LOGI("screen is off and uid: %{public}d is not freeze, start accounting", pkgInfo->GetUid());
pkgInfo->StartAccounting();
}
}
}
}
}