/*
* 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 screen from '@ohos.screen';
import display from '@ohos.display';
import window from '@ohos.window';
import Settings from '@ohos.settings';
import Display from '@ohos.display';
import { BusinessError } from '@ohos.base';
import { LogUtil } from '../utils/LogUtil';
import { displayDataManager } from '../data/DisplayDataManager';
import { PreferencesUtil } from './PreferencesUtil';
import { SettingsDataUtils } from './SettingsDataUtils';
import { DisplayConstant } from '../constant/DisplayConstant';
import { CheckEmptyUtils } from '../utils/CheckEmptyUtils';
import { DeviceUtil } from './BaseUtils';
const TAG = 'DpiManager :';
/**
* dpi和档位描述的集合
*/
interface DensitySet {
density: number;
description: Resource;
}
/**
* OpenHarmony支持的最大dpi数值
*/
const MAX_LCD_DENSITY: number = 640;
/**
* OpenHarmony支持的最小dpi数值
*/
const MIN_LCD_DENSITY: number = 321;
/**
* 最小允许的屏幕维度
*/
const MIN_DIMENSION_DP: number = 320;
/**
* 最大支持的档位
*/
const MAX_SCALE: number = 1.5;
/**
* 最小支持的档位
*/
const MIN_SCALE: number = 0.85;
/**
* 档位的最小颗粒
*/
const MIN_SCALE_INTERVAL: number = 0.09;
/**
* 折叠屏档位的最小颗粒
*/
const ALT_MIN_SCALE_INTERVAL: number = 0.08;
/**
* 默认DPI数值键值
*/
const DEFAULT_DPI_VALUE: string = 'system_default_dpi_value';
/**
* xml读取的默认DPI数值
*/
const DEFAULT_XML_DPI_VALUE: string = 'default_display_dpi';
/**
* 外屏默认DPI数值键值
*/
const EXTEND_DPI_VALUE: string = 'extend_dpi_value';
/**
* 外屏用户自定义DPI数值键值
*/
const USER_SET_DPI_VALUE_EXTEND: string = 'user_set_dpi_value_extend';
/**
* Settings data中的默认DPI数值
*/
const DPI_VALUE_NULL: string = '';
/**
*当前设备折叠态宽度
*/
const FOLD_WIDTH_VALUE: number = 1080;
const SETTINGS_DISPLAY_SCREEN_WIDTH: string = 'settings.display.screen.width';
const DOMAIN_NAME: string = Settings.domainName.USER_PROPERTY;
/**
* DPI管理工具类
*
* @since 2023-08-10
*/
class DpiManager {
/**
* 默认DPI数值
*/
public defaultDensityDpi: number = -1;
/**
* 最大DPI数值
*/
public maxDensityDpi: number = -1;
/**
* 最小DPI数值
*/
public minDensityDpi: number = 1001;
/**
* 当前DPI数值
*/
public currentDensityDpi: number = -1;
/**
* 当前设备支持大档位个数
*/
public numLarger: number = 0;
/**
* 当前设备支持小档位个数
*/
public numSmaller: number = 0;
/**
* 当前设备的档位集合
*/
public densityList: DensitySet[] = [];
/**
* Screen实例
*/
public screenClass: screen.Screen | null = null;
/**
* Display实例
*/
public displayClass: display.Display | null = null;
/**
* Windows实例
*/
public windowClass: window.Window | null = null;
/**
* Windows宽度
*/
public windowWidth: number = 0;
/**
* Windows高度
*/
public windowHeight: number = 0;
/**
* px转化成vp的倍数
*/
public densityMedium: number = 160;
/**
* 导航栏默认宽度
*/
public navBarWidth: number = 271;
/**
* DPI管理获取所需的screen和display接口实例
*
* @since 2023-08-30
*/
async getClassInstance(): Promise<void> {
/* instrument ignore next */
try {
this.displayClass = display.getDefaultDisplaySync();
await this.getScreenClassInstance();
// 当前页面选中设备
if (DeviceUtil.isDevicePc()) {
this.displayClass = Display.getDisplayByIdSync(this.screenClass?.id ?? 0);
}
} catch (err) {
LogUtil.error(`${TAG} get all default display error: ${err?.code}`);
}
}
private async getDisplayClass(currentDeviceId: number): Promise<void> {
try {
let allDisplay: display.Display[] = await display.getAllDisplays();
// 根据当前选中设备获取displayClass
allDisplay.forEach((d) => {
/* instrument ignore if*/
if (d.id === currentDeviceId) {
this.displayClass = d;
}
});
} catch (err) {
LogUtil.error(`${TAG} get all display error: ${err.code}`);
this.displayClass = display.getDefaultDisplaySync();
}
}
private async getScreenClassInstance(): Promise<void> {
let currentDeviceId: number =
Number(SettingsDataUtils.getSettingsData('display_current_device_id', '0', DOMAIN_NAME));
try {
let data: screen.Screen[] = await screen.getAllScreens();
let maxWidth: number = 0;
if (data !== undefined && data !== null && data.length !== 0) {
for (let index = 0; index < data.length; index++) {
// 这里要防止 rsId 为-1的异常值的情况
if (currentDeviceId === (data[index].rsId >= 0 ? data[index].rsId : data[index].id)) {
LogUtil.info(`${TAG} screen index: ${index}`);
this.screenClass = data[index];
LogUtil.showInfo(TAG, `supportedModeInfo length: ${this.screenClass.supportedModeInfo.length}`);
if (this.screenClass.supportedModeInfo.length > 0) {
this.screenClass.supportedModeInfo.forEach((value: screen.ScreenModeInfo, index: number) => {
if (maxWidth < value.width) {
maxWidth = value.width;
}
});
}
break;
}
}
}
LogUtil.info(`${TAG} screen. maxWidth : ${maxWidth}`);
PreferencesUtil.put(SETTINGS_DISPLAY_SCREEN_WIDTH, maxWidth);
} catch (err) {
LogUtil.error(`${TAG} get screen class error. Cause:code: ${err.code} message: ${err.message}`);
}
}
/**
* DPI管理获取所需的window接口实例
*
* @since 2023-09-10
*/
async getWindowClassInstance(): Promise<void> {
try {
this.windowClass = await window.getLastWindow(getContext(this));
this.windowWidth = this.windowClass.getWindowProperties().windowRect.width;
this.windowHeight = this.windowClass.getWindowProperties().windowRect.height;
} catch (err) {
LogUtil.error(`${TAG} Failed to obtain the top window. Cause: code: ${err.code} message: ${err.message}`);
}
}
/**
* 设置dpi,并获取滑块对应的显示大小类型(小、默认、大、较大、最大)
*
* @since 2023-08-30
*/
setDpiName(index: number): Resource {
try {
AppStorage.setOrCreate('dpiChange', index);
this.screenClass?.setDensityDpi(this.densityList[index].density)?.then((data) => {
LogUtil.info(`${TAG} Succeeded in setting the pixel density of the screen.`);
/* instrument ignore else*/
if (this.isNativeDisplay()) {
SettingsDataUtils.setSettingsData(DisplayConstant.SETTINGS_DATA_USER_SET_DPI_VALUE,
this.densityList[index].density.toString());
} else {
// 非本机设备设置dpi
SettingsDataUtils.setSettingsData(USER_SET_DPI_VALUE_EXTEND, this.densityList[index].density.toString());
}
}).catch((err: BusinessError) => {
LogUtil.error(`${TAG} Failed to set the pixel density of the screen. Code: code: ${err.code} message: ${err.message}`);
});
} catch (err) {
LogUtil.error(`${TAG} Failed to set the pixel density of the screen. code: ${err.code} message: ${err.message}`);
}
return this.densityList[index].description;
}
/**
* 设置dpi,并获取滑块对应的显示大小类型(小、默认、大、较大、最大)
*
* @since 2023-08-30
*/
getDpiValue(index: number): number {
return this.densityList[index]?.density;
}
/**
* 获取DPI,并获取滑块对应的显示大小类型(小、默认、大、较大、最大)
*
* @since 2024-01-16
*/
getDpiName(index: number): Resource {
return this.densityList[index]?.description;
}
/**
* 根据DPI获取档位(小、默认、大、较大、最大)
*
* @param dpi dpi值
* @returns 档位
*/
getIndexByDpi(dpi: number): number {
for (let i = 0; i < this.densityList.length; i++) {
if (this.densityList[i].density === dpi) {
return i;
}
}
return 1;
}
/**
* 从settings data中获取默认dpi(若setting data不存在,则将当前dpi设置为默认dpi)
*
* @since 2023-08-30
*/
setDefaultDpi(dpiValue: string): void {
let defaultDpi = PreferencesUtil.getSync(DEFAULT_XML_DPI_VALUE,'') as string
if (!defaultDpi) {
LogUtil.showInfo(TAG, `getDefaultDpi: ${defaultDpi}, displayClass dpi: ${this.displayClass?.densityDPI}`);
defaultDpi = this.displayClass?.densityDPI.toString()??""
PreferencesUtil.putSync(DEFAULT_XML_DPI_VALUE, this.displayClass?.densityDPI.toString()??'')
}
LogUtil.info(`${TAG} setdpiValue: ${dpiValue} setDefaultDpi`+defaultDpi);
/* instrument ignore if*/
if (dpiValue === DPI_VALUE_NULL) {
if (this.isNativeDisplay()) {
LogUtil.showInfo(TAG, `defaultDpi1: ${defaultDpi}, displayClass dpi: ${this.displayClass?.densityDPI}`);
SettingsDataUtils.setSettingsData(DEFAULT_DPI_VALUE, defaultDpi);
displayDataManager.updateDefaultDPI();
this.defaultDensityDpi = Number(defaultDpi);
} else {
SettingsDataUtils.setSettingsDataAsync(EXTEND_DPI_VALUE, this.displayClass?.densityDPI.toString() as string);
displayDataManager.updateExtendDPI();
this.defaultDensityDpi = this.displayClass?.densityDPI as number;
}
} else {
this.defaultDensityDpi = Number.parseInt(dpiValue);
}
if (this.isNativeDisplay()) {
// 默认dpi统一从DEFAULT_XML_DPI_VALUE获取
if (defaultDpi !== dpiValue) {
LogUtil.showWarn(TAG, `defaultDpi2: ${defaultDpi}, dpiValue: ${dpiValue}`);
this.defaultDensityDpi = Number(defaultDpi);
displayDataManager.updateDefaultDPI();
SettingsDataUtils.setSettingsData(DEFAULT_DPI_VALUE, defaultDpi);
}
}
LogUtil.info(`${TAG} this.defaultDensityDpi: ${this.defaultDensityDpi}`);
this.maxDensityDpi = this.defaultDensityDpi;
this.currentDensityDpi = this.displayClass?.densityDPI as number;
}
/**
* 双向截断约束,所有数值不得低于low,不得高于high
*
* @since 2023-08-30
*/
private constrain(amount: number, low: number, high: number): number {
return amount < low ? low : (amount > high ? high : amount);
}
/**
* 将dpi和档位描述打包成为DensitySet
*
* @since 2023-08-30
*/
private getDensitySet(density: number, str: Resource): DensitySet {
let set: DensitySet = {
density: density,
description: str
};
return set;
}
/**
* 计算最高档位和最低档位数量,并将对应档位和描述进行计算;
* 将当前界面所属dpi找到对应档位,并返回当前档位
*
* @since 2023-08-30
*/
setNumberAndScale(summariesLarger: Array<Resource>, summariesSmaller:
Array<Resource>, summariesDefault: Resource): number {
let defaultDensity: number = this.defaultDensityDpi;
let currentDensity: number = this.displayClass?.densityDPI as number;
if (DeviceUtil.isDevicePc()) {
currentDensity = Number(SettingsDataUtils.getSettingsData(DisplayConstant.SETTINGS_DATA_USER_SET_DPI_VALUE,
`${this.displayClass?.densityDPI}`));
}
LogUtil.info(`${TAG} setNumberAndScale: defaultDensity: ${defaultDensity}, currentDensity: ${currentDensity}`);
let currentWidth: string = String(PreferencesUtil.getSync(SETTINGS_DISPLAY_SCREEN_WIDTH,
Math.min(this.displayClass?.height as number, this.displayClass?.width as number)));
let minDimensionPx: number = Display.isFoldable() ? FOLD_WIDTH_VALUE : Number(currentWidth);
LogUtil.info(`${TAG} setNumberAndScale: currentWidth: ${currentWidth}, minDimensionPx: ${minDimensionPx}`);
let maxDensity: number = this.densityMedium * minDimensionPx / MIN_DIMENSION_DP;
let maxScale: number = Math.min(MAX_SCALE, maxDensity / defaultDensity);
let minScale: number = MIN_SCALE;
let minInterval: number = Display.isFoldable() ? ALT_MIN_SCALE_INTERVAL : MIN_SCALE_INTERVAL;
LogUtil.info(`${TAG} setNumberAndScale: ${maxDensity}, ${maxScale}, ${minScale}, ${minInterval}`);
this.numLarger = this.constrain(Math.floor((maxScale - 1) / minInterval), 0, summariesLarger.length);
this.numSmaller = this.constrain(Math.floor((1 - minScale) / MIN_SCALE_INTERVAL), 0, summariesSmaller.length);
this.densityList = [];
LogUtil.info(`${TAG} setNumberAndScale: numLarger:${this.numLarger}, numSmaller:${this.numSmaller}`);
let inSetValue: number = 0;
let currentDensityIndex = this.pushDensity(
minScale, maxScale, defaultDensity, currentDensity,
summariesLarger, summariesSmaller, summariesDefault
);
LogUtil.info(`${TAG} this.currentDensityIndex: ${currentDensityIndex}`);
if (currentDensityIndex >= 0) {
inSetValue = currentDensityIndex;
} else {
inSetValue = 1;
}
LogUtil.info(`${TAG} this.inSetValue: ${inSetValue}`);
LogUtil.info(`${TAG} this.numLarger: ${this.numLarger}`);
return inSetValue;
}
private pushDensity(minScale: number, maxScale: number, defaultDensity: number, currentDensity: number,
summariesLarger: Array<Resource>, summariesSmaller: Array<Resource>, summariesDefault: Resource) {
let curIndex: number = 0;
let currentDensityIndex: number = -1;
let interval: number = 0;
let i: number = 0;
let density: number = 0;
// 对于小于默认的档位,density 不应被强制提升到一个高于默认值的全局常量,
// 否则在某些设备(如部分 PC)上,“最小”档位可能反而比“默认”更大。
// 这里使用 defaultDensity 与 MIN_LCD_DENSITY 中的较小值作为下限,保证最小档位不会大于默认档位。
const minDensityLimit: number = Math.min(defaultDensity, MIN_LCD_DENSITY);
if (this.numSmaller > 0) {
interval = (1 - minScale) / this.numSmaller;
for (i = this.numSmaller - 1; i >= 0; i--) {
density = Math.floor(defaultDensity * (1 - (i + 1) * interval)) & ~1;
if (density < minDensityLimit) {
density = minDensityLimit;
}
if (density < this.minDensityDpi) {
this.minDensityDpi = density;
}
if (currentDensity === density) {
currentDensityIndex = curIndex;
}
this.densityList.push(this.getDensitySet(density, summariesSmaller[i]));
LogUtil.info(`${TAG} setNumberAndScale: densityList.push density:${density}`);
curIndex++;
}
}
if (currentDensity === defaultDensity) {
currentDensityIndex = curIndex;
}
this.densityList.push(this.getDensitySet(defaultDensity, summariesDefault));
LogUtil.info(`${TAG} setNumberAndScale: densityList.push defaultDensity:${defaultDensity}`);
curIndex++;
if (this.numLarger > 0) {
interval = (maxScale - 1) / this.numLarger;
for (i = 0; i < this.numLarger; i++) {
density = Math.floor(defaultDensity * (1 + (i + 1) * interval)) & ~1;
if (density > MAX_LCD_DENSITY) {
density = MAX_LCD_DENSITY;
}
if (density > this.maxDensityDpi) {
this.maxDensityDpi = density;
}
if (currentDensity === density) {
currentDensityIndex = curIndex;
}
this.densityList.push(this.getDensitySet(density, summariesLarger[i]));
LogUtil.info(`${TAG} setNumberAndScale: densityList.push density:${density}`);
curIndex++;
}
}
return currentDensityIndex;
}
private isNativeDisplay() {
// 这里要防止 rsId 为-1的异常值的情况
return (this.screenClass?.rsId && this.screenClass?.rsId >= 0 ? this.screenClass?.rsId : this.displayClass?.id) === 0;
}
}
export default new DpiManager();