* Copyright (c) Huawei Device Co., Ltd. 2024-2025. All rights reserved.
* 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.
*/
import { LogDomain, LogHelper, TaskpoolUtil } from '@ohos/basicutils';
import { SCBOobeManager } from '@ohos/windowscene';
import commonEventManager from '@ohos.commonEventManager';
import type { BusinessError } from '@ohos.base';
import batteryInfo from '@ohos.batteryInfo';
import power from '@ohos.power';
import { BoostState, EmergencyBatteryThreshold } from '../statemanager/DesktopMode';
import { systemParameter } from '@kit.BasicServicesKit';
import { DesktopModeManager } from '../statemanager/DesktopModeManager';
import { BaseModeState } from '../statemanager/modelstate/BaseModeState';
import { EmergencyThresholdManager } from './EmergencyThresholdManager';
const TAG = 'EmergencyEventManager';
const log: LogHelper = LogHelper.getLogHelper(LogDomain.HOME, TAG);
const EMC_SCENE_NAME = 'low_power';
const BOOST_POWER_OFF_STATE = 'BATTERY_EXIT_ECM=2';
const EMC_DELAY_TIME = 30000;
const INVALID_VALUE = -1;
* EMC interaction preprocessing, including all cases for entering and exiting the emergency mode
*/
export class EmergencyEventManager {
static EMC_FLAG_NAME = 'EmergencyViewFlag';
private static instance: EmergencyEventManager;
private mSubscriber: commonEventManager.CommonEventSubscriber | null = null;
private subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
events: [
commonEventManager.Support.COMMON_EVENT_BATTERY_CHANGED
]
};
private isExitEmergencyManually: boolean = false;
private prevDevicePowerMode: power.DevicePowerMode = power.DevicePowerMode.MODE_NORMAL;
private emergencyEventTimer: number = INVALID_VALUE;
private emergencyEnterThreshold: number = EmergencyThresholdManager.getInstance().getEmergencyEnterThreshold();
private emergencyExitThreshold: number = EmergencyThresholdManager.getInstance().getEmergencyExitThreshold();
private batteryChangeCallback = (err: BusinessError, data: commonEventManager.CommonEventData): void => {
if (err) {
log.showError(`Can't handle common event, err: ${err.code}, err: ${err.message}`);
return;
}
if (data.event === commonEventManager.Support.COMMON_EVENT_BATTERY_CHANGED) {
log.showDebug(`battery changed,battery is ${batteryInfo.batterySOC}`);
let batterySOC: number = batteryInfo.batterySOC;
let curPowerMode: power.DevicePowerMode = this.getPowerMode();
let isBatteryNotLow = batterySOC > this.emergencyExitThreshold &&
curPowerMode !== power.DevicePowerMode.MODE_EXTREME_POWER_SAVE;
if (isBatteryNotLow) {
return;
}
if (!this.isBatteryConfigSupported()) {
log.showWarn('Current device does not support boost');
return;
}
if (SCBOobeManager.isOobeActivated()) {
log.showWarn('Oobe is not allowed to go into emergency mode');
this.oobeExitEmergencyMode(curPowerMode);
return;
}
this.enterOrExitEmergencyView(curPowerMode, data);
}
};
private enterOrExitEmergencyView(curPowerMode: power.DevicePowerMode,
data: commonEventManager.CommonEventData): void {
log.showInfo(`enterOrExitEmergencyView, curPowerMode:${curPowerMode}`);
if (this.shouldEnterEmergencyMode()) {
this.eventWhenEnterThreshold(data, curPowerMode);
return;
}
if (this.shouldExitEmergencyMode(curPowerMode)) {
this.exitEmergencyMode();
}
}
private shouldEnterEmergencyMode(): boolean {
let batterySOC: number = batteryInfo.batterySOC;
let emergencyManualFlag: boolean = AppStorage.get('emergencyManualFlag') ?? false;
if (emergencyManualFlag && batterySOC >= this.emergencyExitThreshold) {
AppStorage.setOrCreate('emergencyManualFlag', false);
}
log.showWarn(`Current batterySOC: %{public}d, chargingState: %{public}d,` +
` flag: %{public}s, enterThreshold: %{public}d, exitThreshold: %{public}d`, batterySOC,
batteryInfo.chargingStatus, emergencyManualFlag, this.emergencyEnterThreshold, this.emergencyExitThreshold);
return batterySOC <= this.emergencyEnterThreshold && !emergencyManualFlag;
}
private shouldExitEmergencyMode(curPowerMode: power.DevicePowerMode): boolean {
return batteryInfo.batterySOC >= this.emergencyExitThreshold && this.isInEmergencyStatus(curPowerMode);
}
private constructor() {
this.abnormalTerminationRecovery();
this.mSubscriber = commonEventManager.createSubscriberSync(this.subscribeInfo);
if (this.mSubscriber !== null) {
commonEventManager.subscribe(this.mSubscriber, this.batteryChangeCallback);
} else {
log.showError('Need create subscriber!');
return;
}
log.showInfo('Init createSubscriberSync end');
}
* 支持应急模式,不处于oobe,电量小于等于阈值且未充电,拉起应急模式倒计时
*/
private abnormalTerminationRecovery(): void {
this.exitEmergencyPowerState();
if (!this.isBatteryConfigSupported()) {
log.showWarn('Current device does not support boost');
return;
}
if (SCBOobeManager.isOobeActivated()) {
log.showWarn('Oobe is not allowed to go into emergency mode');
return;
}
if (this.isGreaterThenEnterThreshold()) {
log.showWarn('the battery is not low');
return;
}
if (this.isInCharging()) {
log.showWarn('the battery is charging');
return;
}
this.eventWhenEnterThreshold(null, power.DevicePowerMode.MODE_NORMAL);
}
private isGreaterThenEnterThreshold(): boolean {
let batterySOC: number = batteryInfo.batterySOC ?? this.emergencyExitThreshold;
return batterySOC > this.emergencyEnterThreshold;
}
* oobe 若是处于应急模式,直接退出
* @param curPowerMode 当前模式
*/
private oobeExitEmergencyMode(curPowerMode: power.DevicePowerMode): void {
if (this.isInEmergencyStatus(curPowerMode)) {
this.exitEmergencyMode();
}
}
private isInEmergencyStatus(curPowerMode: power.DevicePowerMode): boolean {
if (curPowerMode === power.DevicePowerMode.MODE_EXTREME_POWER_SAVE) {
return true;
}
let emergencyViewFlag = AppStorage.get<boolean>(EmergencyEventManager.EMC_FLAG_NAME);
if (emergencyViewFlag) {
return true;
}
return false;
}
private eventWhenEnterThreshold(data: commonEventManager.CommonEventData | null,
curPowerMode: power.DevicePowerMode): void {
if (curPowerMode !== power.DevicePowerMode.MODE_EXTREME_POWER_SAVE) {
if (data && data.parameters?.uevent === BOOST_POWER_OFF_STATE) {
this.powerOffShutdown('exit_uevent');
return;
}
this.prevDevicePowerMode = curPowerMode;
let isBatteryCharged: boolean = this.isInCharging() || batteryInfo.batterySOC >= this.emergencyExitThreshold;
if (isBatteryCharged) {
log.showInfo('clear emergencyEventTimer');
this.clearTimer();
return;
}
if (this.emergencyEventTimer !== INVALID_VALUE) {
log.showInfo(`this.emergencyEventTimer is existed: ${this.emergencyEventTimer}`);
return;
}
this.emergencyEventTimer = setTimeout(() => {
this.enterEmergencyMode();
}, EMC_DELAY_TIME);
} else {
if (batteryInfo.chargingStatus === batteryInfo.BatteryChargeState.ENABLE) {
this.setBatteryConfig(BoostState.RECOVERY_BOOST);
return;
}
let isBatteryTooLow: boolean = (batteryInfo.batterySOC <= EmergencyBatteryThreshold.CLOSE_THRESHOLD ||
(data && data.parameters?.uevent === BOOST_POWER_OFF_STATE)) ?? false;
if (isBatteryTooLow) {
this.exitEmergencyMode();
this.powerOffShutdown('exit_emergency');
return;
}
}
}
private powerOffShutdown(name: string): void {
try {
power.shutdown(name);
log.showInfo('Exit POWER_OFF success! reason:' + name);
} catch (error) {
log.showError('Exit POWER_OFF error! reason:' + name);
}
}
* Determines whether the current device supports the boost circuit.
* @returns
*/
public isBatteryConfigSupported(): boolean {
let isSupportBoost: boolean = false;
try {
let isFactoryMode: boolean = systemParameter.getSync('const.runmode', 'default') === 'factory';
isSupportBoost = !isFactoryMode && batteryInfo.isBatteryConfigSupported(EMC_SCENE_NAME);
} catch (err) {
log.showError(`Get isBatteryConfigSupported failed, err: ${err.code}, errMessage: ${err.message}`);
}
return isSupportBoost;
}
private getPowerMode(): power.DevicePowerMode {
let curPowerMode: power.DevicePowerMode = power.DevicePowerMode.MODE_NORMAL;
try {
curPowerMode = power.getPowerMode();
} catch (err) {
log.showError(`Get device power mode failed , err: ${err.code}, errMessage: ${err.message}`);
}
return curPowerMode;
}
private setPowerMode(mode: power.DevicePowerMode): void {
power.setPowerMode(mode).then(() => {
log.showInfo(`Set device power mode to ${mode}` + ` success`);
}).catch((err: Error) => {
log.showError(`Set device power mode failed, errMessage: ${err.message}`);
});
}
exitEmergencyPowerState(): void {
let curPowerMode: power.DevicePowerMode = this.getPowerMode();
if (curPowerMode === power.DevicePowerMode.MODE_EXTREME_POWER_SAVE) {
this.setPowerMode(power.DevicePowerMode.MODE_NORMAL);
this.setBatteryConfig(BoostState.RECOVERY_BOOST);
}
}
private enterEmergencyPowerState(): void {
this.setPowerMode(power.DevicePowerMode.MODE_EXTREME_POWER_SAVE);
this.setBatteryConfig(BoostState.TRIGGER_BOOST);
}
private setBatteryConfig(sceneValue: string): void {
TaskpoolUtil.doTask(setBatteryConfig, sceneValue);
}
private enterEmergencyMode(): void {
this.clearTimer();
this.enterEmergencyPowerState();
}
private clearTimer(): void {
if (this.emergencyEventTimer !== INVALID_VALUE) {
clearTimeout(this.emergencyEventTimer);
this.emergencyEventTimer = INVALID_VALUE;
}
}
private exitEmergencyMode(isExitEmergencyManually: boolean = false): void {
this.isExitEmergencyManually = isExitEmergencyManually;
log.showInfo(`Exit emergency mode, isExitEmergencyManually is ${isExitEmergencyManually}`);
if (this.isEmergencyViewError()) {
log.showInfo('Not in emergency mode..');
DesktopModeManager.getInstance().changeModeStateTo(BaseModeState.getInstance());
return;
}
this.setPowerMode(this.prevDevicePowerMode);
this.setBatteryConfig(BoostState.RECOVERY_BOOST);
}
* 多用户状态不一致的时候会进入这个逻辑,如开机第一次从主空间拉起隐私空间,隐私空间充电,且电量达到2%临界点
*
* @returns true 错误进图多用户状态
*/
private isEmergencyViewError(): boolean {
let curPowerMode: power.DevicePowerMode = this.getPowerMode();
let emergencyViewFlag = AppStorage.get<boolean>(EmergencyEventManager.EMC_FLAG_NAME);
return (curPowerMode !== power.DevicePowerMode.MODE_EXTREME_POWER_SAVE && emergencyViewFlag) ?? false;
}
* Exiting the emergency mode manually
*/
public exitEmergencyModeManually(): void {
this.exitEmergencyMode(true);
let batterySOC: number = batteryInfo.batterySOC ?? 0;
let isBatteryTooLow: boolean = !this.isInCharging() && batterySOC <= this.emergencyEnterThreshold;
if (isBatteryTooLow) {
this.powerOffShutdown('exit_emergency');
}
}
* Is in charging state
* @returns
*/
public isInCharging(): boolean {
let chargingState: batteryInfo.BatteryChargeState = batteryInfo.chargingStatus;
log.showInfo(`Current charging state: ${chargingState}`);
return chargingState === batteryInfo.BatteryChargeState.ENABLE || chargingState === batteryInfo.BatteryChargeState.FULL;
}
* Get the flag indicating whether to manually exit the emergency mode.
* @returns isExitEmergencyManually
*/
public getIsExitEmergencyManually(): boolean {
return this.isExitEmergencyManually;
}
public static getInstance(): EmergencyEventManager {
if (!EmergencyEventManager.instance) {
EmergencyEventManager.instance = new EmergencyEventManager();
}
return EmergencyEventManager.instance;
}
}
* 在子线程调用batteryInfo.setBatteryConfig
*
* @param sceneValue 场景值
*/
function setBatteryConfig(sceneValue: string): void {
'use concurrent';
const TAG = 'EmergencyEventManager';
const log: LogHelper = LogHelper.getLogHelper(LogDomain.SCB, TAG);
try {
const SCENE_NAME = 'low_power';
let setRes = batteryInfo.setBatteryConfig(SCENE_NAME, sceneValue);
log.showInfo(`setBatteryConfig setRes: ${setRes}`);
} catch (err) {
log.showError(`Set battery config failed, err: ${err.code}, errMessage: ${err.message}`);
}
}