* Copyright (c) Huawei Technologies 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 Display from '@ohos.display';
import type common from '@ohos.app.ability.common';
import { LogUtil } from '../utils/LogUtil';
import type { Context } from '@ohos.abilityAccessCtrl';
import Settings from '@ohos.settings';
import { SettingsDataUtils } from '../utils/SettingsDataUtils';
const TAG = 'SettingsDisplayManager';
const ROTATION_90 = 1;
const ROTATION_270 = 3;
const NOTCH_FIX_WIDTH = 12;
export const EVENT_ID_FOLD_STATUS_CHANGE: number = 5000001;
export const EVENT_KEY_FOLD_STATUS_CHANGE: string = 'event_key_fold_status_change';
const DEFAULT_DPI_VALUE: string = 'system_default_dpi_value';
export type OffEvent = () => void;
* 屏幕常量
*/
export class DisplayConstants {
* 默认display id
*/
public static readonly DEFAULT_DISPLAY: number = 0;
* 无效display id
*/
public static readonly INVALID_DISPLAY: number = -1;
}
* 屏幕显示信息
*/
export class DisplayState {
public rotation: number;
public cutoutInfo: CutoutInfo;
* 构造
*/
constructor() {
this.cutoutInfo = new CutoutInfo(0, 0, 0, 0);
}
* 对比方法,校验是否一致
*
* @param other DisplayState
*/
public equals(other: DisplayState): boolean {
if (this.rotation !== other.rotation) {
return false;
}
if (!this.cutoutInfo.equals(other.cutoutInfo)) {
return false;
}
return true;
}
}
* 挖孔信息
*/
export class CutoutInfo {
public left: number;
public top: number;
public right: number;
public bottom: number;
* 构造
*/
constructor(left: number, top: number, right: number, bottom: number) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
* 对比方法,校验是否一致
*
* @param other 另一个CutoutInfo
*/
public equals(other: CutoutInfo): boolean {
if (this.left !== other.left || this.top !== other.top ||
this.right !== other.right || this.bottom !== other.bottom) {
return false;
}
return true;
}
}
* 屏幕显示信息回调
*/
export interface Callback {
(data: DisplayState): void;
}
* display屏幕管理
*
* @since 2023-2-27
*/
export class DisplayManager {
* 当前运行屏幕id
*/
private displayId?: number;
* 当前运行屏幕实例
*/
private display?: Display.Display;
* 折叠屏展开状态
*/
private foldStatus?: number;
* 当前运行屏幕显示信息实例
*/
private displayState?: DisplayState;
* 监听集合
*/
private listener?: Set<Callback>;
* 默认DPI
*/
private defaultDensityDpi: number = -1;
* 构造
*/
constructor() {
this.listener = new Set();
this.displayState = new DisplayState();
}
* 获取显示管理类对象
*
* @return 显示管理类对象单一实例
*/
public static getInstance(): DisplayManager {
if (!Boolean(AppStorage.get('DisplayManager')).valueOf()) {
AppStorage.setOrCreate<DisplayManager>('DisplayManager', new DisplayManager());
}
return AppStorage.get('DisplayManager') as DisplayManager;
}
* display初始化
*/
public init(context: common.AbilityStageContext): void {
if (!context) {
return;
}
this.displayId = context.config?.displayId;
if (this.displayId === DisplayConstants.INVALID_DISPLAY) {
this.displayId = DisplayConstants.DEFAULT_DISPLAY;
}
LogUtil.info(TAG + `init current display id: ${this.displayId}`);
this.refreshCurrentDisplay().then(() => this.checkDisplayState());
Display.on('change', (id) => {
if (id === this.displayId) {
LogUtil.info(TAG + `display changed id: ${id}`);
this.refreshCurrentDisplay().then(() => this.checkDisplayState());
}
});
}
* 获取DPI相对默认值的缩放比例
*
* @returns 缩放比例
*/
public getDpiScale(): number {
if (this.defaultDensityDpi === -1) {
this.getDefaultDpi();
}
if (this.defaultDensityDpi !== -1 && this.display?.densityDPI && this.display.densityDPI !== 0) {
let scale: number = this.defaultDensityDpi / this.display?.densityDPI;
return Number.isNaN(scale) ? 1 : scale;
}
return 1;
}
getDefaultDpi(): void {
if (AppStorage.get<Context>('pageContext') === undefined) {
LogUtil.error(`${TAG} getDefaultDpi context null`);
return;
}
const dpiValue: string = SettingsDataUtils.getSettingsData(DEFAULT_DPI_VALUE, '');
LogUtil.info(`${TAG} dpiValue: ${dpiValue}`);
if (dpiValue === '') {
let currentDpi: string = this.display?.densityDPI.toString() ?? '';
SettingsDataUtils.setSettingsData( DEFAULT_DPI_VALUE, currentDpi);
this.defaultDensityDpi = this.display?.densityDPI as number;
} else {
this.defaultDensityDpi = Number.parseInt(dpiValue);
}
LogUtil.info(`${TAG} this.defaultDensityDpi: ${this.defaultDensityDpi}`);
}
* 检查屏幕信息是否改变,如果改变则需要通知监听者
*/
private checkDisplayState(): void {
this.display.getCutoutInfo().then(data => {
let displayState = new DisplayState();
displayState.rotation = this.display.rotation;
displayState.cutoutInfo = this.refreshCutoutInfo(this.display, data.boundingRects);
if (this.displayState.equals(displayState)) {
LogUtil.info(TAG + ' displayState is not change!');
return;
}
this.displayState = displayState;
LogUtil.info(TAG + ' checkDisplayState');
this.listener?.forEach(callback => callback(this.displayState));
});
}
* 注册屏幕信息改变监听
*
* @param callback 改变后的回调
*/
public on(callback: Callback): OffEvent {
if (!callback) {
return () => {};
}
this.listener.add(callback);
LogUtil.info(TAG + ' on callback, size: ' + this.listener?.size);
if (this.displayState) {
callback(this.displayState);
}
return () => this.off(callback);
}
* 反注册屏幕信息改变监听
*
* @param callback 改变后的回调
*/
public off(callback: Callback): void {
if (!callback) {
return;
}
let result = this.listener?.delete(callback);
LogUtil.info(TAG + ' off callback, size: ' + this.listener?.size);
if (!result) {
LogUtil.error(TAG + 'off callback failed');
}
}
* 直接获取缓存display,不用异步等待
*
* @return display(Nullable)
*/
public getCacheDisplay(): Display.Display {
return this.display as Display.Display;
}
* 获取当前display
*
* @return 当前display
*/
public async getCurrentDisplay(): Promise<Display.Display> {
if (this.display) {
return new Promise(resolve => resolve(this.display));
}
await this.refreshCurrentDisplay();
return new Promise(resolve => resolve(this.display));
}
* 设置折叠屏状态
*
* @param status 折叠屏状态
*/
public setFoldStatus(status: number): void {
this.foldStatus = status;
}
* 获取折叠屏状态
*
* @returns 折叠屏状态
*/
public getFoldStatus(): number {
return this.foldStatus;
}
* 刷新当前display
*/
private async refreshCurrentDisplay(): Promise<void> {
let allDisplays = await Display.getAllDisplays();
allDisplays?.find((display) => {
if (display && this.displayId === display?.id) {
LogUtil.info(TAG + ' refreshCurrentDisplay display.');
this.display = display;
}
});
}
* 刷新挖孔信息
*/
private refreshCutoutInfo(display: Display.Display, cutoutRects: Array<Display.Rect>): CutoutInfo {
let cutoutInfo = new CutoutInfo(0, 0, 0, 0);
if (!display || !cutoutRects || cutoutRects.length === 0) {
return cutoutInfo;
}
let rect = cutoutRects[0];
switch (display.rotation) {
case ROTATION_90:
cutoutInfo.right = display.width - rect?.left + NOTCH_FIX_WIDTH;
break;
case ROTATION_270:
cutoutInfo.left = rect?.left + rect?.width + NOTCH_FIX_WIDTH;
break;
default:
return cutoutInfo;
}
return cutoutInfo;
}
}