/*
* 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 { CommonUtils, LogDomain, LogHelper } from '@ohos/basicutils';
import { DeviceHelper, EvtBus, HiDfxEventUtil, OuterHomeCallbackEvent } from '@ohos/frameworkwrapper';
import { SCBScreenSessionManager, SCBTransitionManager, SCBWindowRotateController } from '@ohos/windowscene';
import { AbstractObserverManager } from '../base/AbstractObserverManager';
import screenOnOffMediator from '../interface/ScreenOnOffMediator';
import { PowerState, PowerStateChangeListener, PowerStateManager } from '../manager/PowerStateManager';
import { ScreenLockStateManager } from '../manager/ScreenLockStateManager';
import { PowerUtils } from '../utils/PowerUtils';
import { LockEventType, SlServerHelper } from '../utils/SlServerHelper';
import { ScreenLockUnlockService } from './ScreenLockUnLockService';
const TAG = 'ScreenOnOffService';
const log: LogHelper = LogHelper.getLogHelper(LogDomain.KG, TAG);
const CODE_BEGIN_WAKE_UP: number = 3;
const EVENT_DATA_BEGIN_WAKE_UP: string = 'BEGIN_WAKE_UP';
const STATE_CHANGE_REASON_PRE_BRIGHT: number = 26;
const STATE_CHANGE_REASON_PRE_BRIGHT_AUTH_FAIL_SCREEN_OFF: number = 29;
const STATE_CHANGE_REASON_AOD_SLIDING: number = 40;
const STATE_CHANGE_REASON_PROXIMITY: number = 32;
/**
* WakeUp原因
*/
export enum WakeUpReason {
/**
* 底部上滑亮屏
*/
AOD_SLIDING,
/**
* 接近光
*/
PROXIMITY,
/**
* 其他亮屏
*/
OTHER,
}
/**
* Sleep原因
*/
export enum SleepReason {
/**
* 接近光
*/
PROXIMITY,
/**
* 其他亮屏
*/
OTHER,
}
/**
* 亮灭屏监听
*/
export interface ScreenOnOffListener {
/**
* 通知预亮屏处理
*/
onPreBright?: () => void;
/**
* 通知预亮屏失败灭屏处理
*/
onPreBrightFailSleep?: () => void;
/**
* 通知开始亮屏处理
*
* @param reason 亮屏原因
*/
onBeginWakeUp?: (reason: WakeUpReason) => void;
/**
* 开始点亮屏幕
*/
onBeginDisplayOn?: () => void;
/**
* 屏幕点亮
*/
onEndDisplayOn?: () => void;
/**
* 开始熄屏前回调
*/
onPreBeginSleep?: (reason: SleepReason) => void;
/**
* 开始熄屏
*/
onBeginSleep?: (reason: SleepReason) => void;
/**
* 屏幕熄灭结束
*/
onEndDisplayOff?: () => void;
/**
* 熄屏结束
*/
onEndSleep?: () => void;
/**
* 屏幕亮灭变化通知
*
* @param isScreenOn 屏幕是否亮
*/
onScreenOnChange?: (isScreenOn: boolean) => void;
/**
* 屏幕亮灭变化通知
*
* @param isIntoSleep 是否处于熄屏过程中
*/
onIntoSleepChange?: (isIntoSleep: boolean) => void;
}
/**
* 定制动效管理
*/
export interface CustomScreenOffAnimationManager {
/**
* 处理定制灭屏事件业务
*
* @param powerState 电源状态
*/
handlePowerEventNext(powerState: string): Promise<void>;
/**
* 处理定制电源Begin Sleep事件
*
* @param callback 定制需要回调的callback
*/
handleBeginSleep(callback: () => void): void;
}
/**
* 亮灭屏服务
*/
export class ScreenOnOffService extends AbstractObserverManager<ScreenOnOffListener> {
public static readonly ENABLE_ROTATE_REASON: string = TAG;
private static sInstance: ScreenOnOffService;
public static getInstance(): ScreenOnOffService {
if (ScreenOnOffService.sInstance == null) {
ScreenOnOffService.sInstance = new ScreenOnOffService();
}
return ScreenOnOffService.sInstance;
}
private _powerStateChangeListener: PowerStateChangeListener = {
onStateChange: (powerState: string, reason: number) => {
this.handlePowerEvent(powerState, reason);
}
};
/**
* 是否亮屏
*/
private _isScreenOn: boolean = true;
/**
* 是否处于熄屏过程中(从BEGIN_SLEEP到END_SLEEP)
*/
private _isIntoSleep: boolean = false;
private _customAnimation?: CustomScreenOffAnimationManager;
/**
* 灭屏原因
* 在每次灭屏时候刷新,亮屏时候获取的是上次灭屏值
*/
private _sleepReason: SleepReason = SleepReason.OTHER;
private constructor() {
super();
}
public get sleepReason(): SleepReason {
return this._sleepReason;
}
public get isScreenOn(): boolean {
return this._isScreenOn;
}
/**
* 设置定制灭屏动效
*
* @param customAnimation 定制动效
*/
public setCustomAnimation(customAnimation: CustomScreenOffAnimationManager | undefined): void {
this._customAnimation = customAnimation;
}
private set isScreenOn(isScreenOn: boolean) {
if (this._isScreenOn !== isScreenOn) {
this._isScreenOn = isScreenOn;
log.showInfo(`isScreenOn to: ${isScreenOn}`);
this.broadcastDataChange(listener => listener?.onScreenOnChange?.(isScreenOn));
}
}
public get isIntoSleep(): boolean {
return this._isIntoSleep;
}
private set isIntoSleep(isIntoSleep: boolean) {
if (this._isIntoSleep !== isIntoSleep) {
this._isIntoSleep = isIntoSleep;
log.showInfo(`isIntoSleep to: ${isIntoSleep}`);
this.broadcastDataChange(listener => listener?.onScreenOnChange?.(isIntoSleep));
}
}
protected doInit(): void {
log.showInfo('doInit');
// 电源监听初始化
PowerStateManager.getInstance().init();
// 注册电源事件监听
PowerStateManager.getInstance().registerObserver(this._powerStateChangeListener);
}
protected doRelease(): void {
log.showInfo('doRelease');
// 电源监听释放
PowerStateManager.getInstance().release();
// 注销电源事件监听
PowerStateManager.getInstance().unRegisterObserver(this._powerStateChangeListener);
}
private handlePowerEvent(powerState: string, reason: number): void {
if (this._customAnimation) {
this.handlePowerEventCustom(powerState, reason);
} else {
this.handlePowerEventNext(powerState, reason);
}
}
private async handlePowerEventCustom(powerState: string, reason: number): Promise<void> {
await this._customAnimation?.handlePowerEventNext(powerState);
this.handlePowerEventNext(powerState, reason);
}
private handlePowerEventNext(powerState: string, reason: number): void {
log.showInfo(`powerState: ${powerState}, reason: ${reason}`);
switch (powerState) {
case PowerState.BEGIN_WAKE_UP:
this.isIntoSleep = false; // 打断场景下,恢复
this.handleBeginWakeUp(reason);
screenOnOffMediator.setScreenOn(true);
break;
case PowerState.BEGIN_DISPLAY_ON:
this.handleBeginDisplayOn(reason);
break;
case PowerState.END_DISPLAY_ON:
this.isScreenOn = true;
this.handleEndDisplayOn(reason);
screenOnOffMediator.setScreenOnEnd(true);
break;
case PowerState.BEGIN_SLEEP:
// 请求锁屏
ScreenLockUnlockService.getInstance().requestLock();
this.isIntoSleep = true;
this.handleBeginSleep(reason);
break;
case PowerState.END_SLEEP:
this.isIntoSleep = false;
this.handleEndSleep(reason);
screenOnOffMediator.setScreenOnEnd(false);
screenOnOffMediator.setScreenOn(false);
break;
case PowerState.END_DISPLAY_OFF:
this.isScreenOn = false;
this.handleEndDisplayOff(reason);
break;
}
}
private handleBeginWakeUp(reason: number): void {
this.setEnableRotate(true);
// 亮屏时重置BeginSleepStart为false
AppStorage.setOrCreate('isBeginSleepStart', false);
switch (reason) {
case STATE_CHANGE_REASON_PRE_BRIGHT:
log.showDebug('reason is STATE_CHANGE_REASON_PRE_BRIGHT.');
this.broadcastDataChange(listener => listener?.onPreBright?.());
break;
case STATE_CHANGE_REASON_AOD_SLIDING:
log.showDebug('reason is STATE_CHANGE_REASON_AOD_SLIDING.');
this.handleNormalBeginWakeUp(WakeUpReason.AOD_SLIDING);
break;
case STATE_CHANGE_REASON_PROXIMITY:
log.showDebug('reason is STATE_CHANGE_REASON_PROXIMITY.');
this.handleNormalBeginWakeUp(WakeUpReason.PROXIMITY);
break;
default:
log.showDebug(`reason is ${reason}.`);
this.handleNormalBeginWakeUp(WakeUpReason.OTHER);
break;
}
}
private handleNormalBeginWakeUp(wakeUpReason: WakeUpReason): void {
// 外部依赖,AOD依赖此设置,否则无法正常启动AOD
screenOnOffMediator.ableToSendScreenOffNotifyEvent = false;
// 外部依赖,outerHome依赖此设置,否则外屏时间不会刷新。如果outerHome自己监听亮屏广播,则会有时间跳变的问题。
this.sendBeginWakeUpToOuterHome();
this.rotateFoldFullScreen();
this.broadcastDataChange(listener => listener?.onBeginWakeUp?.(wakeUpReason));
}
/**
* 向OuterHome发送begin wake up消息
*/
public sendBeginWakeUpToOuterHome(): void {
if (DeviceHelper.isSmallFoldProduct() && DeviceHelper.isSubDisplayMode()) {
log.showInfo('sendBeginWakeUpToOuterHome');
let outerHomeCallbackEvent = new OuterHomeCallbackEvent(CODE_BEGIN_WAKE_UP, EVENT_DATA_BEGIN_WAKE_UP);
EvtBus.post(OuterHomeCallbackEvent, outerHomeCallbackEvent);
}
}
private setEnableRotate(enableRotate: boolean): void {
try {
const session = SCBScreenSessionManager.getInstance().getMainScreenSession();
if (session) {
session.setEnableRotate(enableRotate, ScreenOnOffService.ENABLE_ROTATE_REASON);
}
} catch (error) {
log.showError(`setEnableRotate fail. code ${error.code}`);
}
}
/**
* 由于灭屏后,AOD显示期间,Sceneboard窗口不可旋转。
* 如果用户在灭屏以后旋转了设备,
* 在收到亮屏事件以后需要恢复窗口到当前设备状态
*/
private rotateFoldFullScreen(): void {
try {
if (DeviceHelper.isPcNot2in1Device() || DeviceHelper.isBarPhone() ||
DeviceHelper.isWatch()) {
return;
}
const session = SCBScreenSessionManager.getInstance().getMainScreenSession();
if (!CommonUtils.isInvalid(session) && SCBWindowRotateController.getInstance().isDesktopRotatable()) {
log.showInfo('rotateFoldFullScreen.');
session.rotationChangeEntry(session.sensorScreenProperty.rotation, 'aod exit');
}
} catch (error) {
log.showError(`rotateFoldFullScreen fail. code ${error.code}`);
}
}
private handleBeginDisplayOn(_reason: number): void {
log.showDebug('notifyBeginScreenOnTransition');
SCBTransitionManager.getInstance().notifyBeginScreenOnTransition();
if (ScreenLockStateManager.getInstance().isPageShow || !ScreenLockStateManager.getInstance().isLocked) {
// 亮屏时:锁屏已经绘制完成或者锁屏已经解锁,直接通知DMS,不阻塞屏幕上电
SlServerHelper.getInstance().sendScreenLockEvent(LockEventType.SCREEN_DRAW_DONE);
}
this.broadcastDataChange(listener => listener?.onBeginDisplayOn?.());
// 外部依赖,状态栏依赖
AppStorage.setOrCreate('isScreenOnStart', true);
}
private handleEndDisplayOn(_reason: number): void {
log.showDebug('notifyEndScreenOnTransition');
SCBTransitionManager.getInstance().notifyEndScreenOnTransition();
this.broadcastDataChange(listener => listener?.onEndDisplayOn?.());
HiDfxEventUtil.reportScreenOnEvent();
}
private handleBeginSleep(reason: number): void {
if (SlServerHelper.getInstance().isScreenLockDisabled()) {
log.showInfo(`isScreenLockDisabled, no need do screen lock.`);
SCBScreenSessionManager.getInstance().notifyScreenLockEvent(PowerUtils.NOTIFY_OFF_SCREEN_CLOSE_LCD);
return;
}
this.setEnableRotate(false);
this._sleepReason = this.convertSleepReason(reason);
this.broadcastDataChange(listener => listener?.onPreBeginSleep?.(this._sleepReason));
AppStorage.setOrCreate('isBeginSleepStart', true);
// 外部依赖
AppStorage.setOrCreate('isShortcutKeyPanelShow', false);
switch (reason) {
case STATE_CHANGE_REASON_PRE_BRIGHT_AUTH_FAIL_SCREEN_OFF:
log.showDebug('reason is STATE_CHANGE_REASON_PRE_BRIGHT_AUTH_FAIL_SCREEN_OFF.');
this.broadcastDataChange(listener => listener?.onPreBrightFailSleep?.());
break;
case STATE_CHANGE_REASON_PROXIMITY:
log.showDebug('The screen is powered off without locking the screen when the proximity sensor is off.');
SCBScreenSessionManager.getInstance().notifyScreenLockEvent(PowerUtils.NOTIFY_OFF_SCREEN_CLOSE_LCD);
break;
default:
this.handleBeginSleepDefaultReason(this._sleepReason);
break;
}
}
private handleBeginSleepDefaultReason(sleepReason: SleepReason): void {
// 外部依赖,AOD依赖此设置,否则无法正常启动AOD
screenOnOffMediator.ableToSendScreenOffNotifyEvent = true;
if (this._customAnimation) {
this._customAnimation.handleBeginSleep(() => {
this.broadcastDataChange(listener => listener?.onBeginSleep?.(sleepReason));
});
} else {
this.broadcastDataChange(listener => listener?.onBeginSleep?.(sleepReason));
}
}
private convertSleepReason(reason: number): SleepReason {
return reason === STATE_CHANGE_REASON_PROXIMITY ? SleepReason.PROXIMITY : SleepReason.OTHER;
}
private handleEndDisplayOff(_reason: number): void {
log.showDebug('handleEndDisplayOff');
this.broadcastDataChange(listener => listener?.onEndDisplayOff?.());
}
private handleEndSleep(_reason: number): void {
this.broadcastDataChange(listener => listener?.onEndSleep?.());
// 外部依赖,状态栏依赖
AppStorage.setOrCreate('isScreenOnStart', false);
AppStorage.setOrCreate('isBeginSleepStart', false);
HiDfxEventUtil.reportScreenOffEvent();
}
}