* 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 { AppItemInfo, AppStatus, CommonConstants } from '../TsIndex';
import { image } from '@kit.ImageKit';
import fileuri from '@ohos.file.fileuri';
import fs from '@ohos.file.fs';
import preferences from '@ohos.data.preferences';
import { CommonUtils, LogDomain, LogHelper, PixelMapUtil } from '@ohos/basicutils/src/main/ets/TsIndex';
import {
GlobalContext,
Nullable,
RdbStoreConfig,
rdbStoreHelper,
sSettingsUtil
} from '@ohos/frameworkwrapper/src/main/ets/TsIndex';
import type ctx from '@ohos.app.ability.common';
import rdb from '@ohos.data.relationalStore';
import GridLayoutInfoColumns from '../db/column/GridLayoutInfoColumns';
import { GraphicUtils } from '@ohos/frameworkwrapper';
import { settings } from '@kit.BasicServicesKit';
const TAG: string = 'RestoreLauncherDataManager';
const log: LogHelper = LogHelper.getLogHelper(LogDomain.BACKUP, TAG);
const ICONS_DIR = '/RestoreIconData/';
const UPGRADE_ICONS_DIR = '/oldIcons/';
const ICONS_COMPATIBILITY = 'deal_clone_icons_scale_compatibility';
const UPGRADE_ICONS_COMPATIBILITY = 'deal_upgrade_icons_scale_compatibility';
* 克隆占位管理类
*
* @since 2025-01-09
*/
export class RestoreLauncherDataManager {
private static instance: RestoreLauncherDataManager;
private readonly filesDir: string =
(GlobalContext.getInstance().getObject('desktopContext') as ctx.ServiceExtensionContext)?.filesDir;
static getInstance(): RestoreLauncherDataManager {
if (RestoreLauncherDataManager.instance == null) {
RestoreLauncherDataManager.instance = new RestoreLauncherDataManager();
}
return RestoreLauncherDataManager.instance;
}
* 处理克隆图标兼容性
*
* @param path 图标路径
* @returns 图标URI
*/
public async dealCloneIconsCompatibility(context: ctx.ServiceExtensionContext): Promise<void> {
this.setShortcutSupportCloneFlag();
let compatibilityType: string = sSettingsUtil.getValue(ICONS_COMPATIBILITY, CompatibilityType.NONE, context);
if (compatibilityType === CompatibilityType.COMPLETED) {
log.showDebug(`icon compatibility has been processed, compatibilityType: ${compatibilityType}`);
return;
}
log.showWarn(`start processing icon compatibility`);
let count: number = 0;
const resultList: AppItemInfo[] = await this.queryCloneIconsUri();
try {
for (const item of resultList) {
if (!item || !item.iconResource || item.iconResource.indexOf(ICONS_DIR) === -1) {
log.showDebug(`the icon resource path is abnormal, iconResource: ${item.iconResource}`);
continue;
}
count++;
await this.getIconScaleUri(item.iconResource);
log.showDebug(`icon resource after enlargement: ${item.iconResource}`);
}
} catch (err) {
log.error('dealCloneIconsCompatibility err:', err);
}
sSettingsUtil.setValue(ICONS_COMPATIBILITY, CompatibilityType.COMPLETED, context);
log.showWarn(`icon compatibility processing completed, length: ${resultList.length}, count: ${count}`);
}
* 处理升级图标兼容性
*
* @param path 图标路径
* @returns
*/
public async dealUpgradeIconsCompatibility(context: ctx.ServiceExtensionContext): Promise<void> {
let compatibilityType: string = sSettingsUtil.getValueEx(settings.domainName.USER_PROPERTY, UPGRADE_ICONS_COMPATIBILITY, CompatibilityType.NONE, context);
if (compatibilityType === CompatibilityType.COMPLETED) {
log.showDebug(`upgrade icon compatibility has been processed, compatibilityType: ${compatibilityType}`);
return;
}
log.showWarn(`start processing upgrade icon compatibility`);
let count: number = 0;
const resultList: AppItemInfo[] = await this.queryCloneIconsUri();
try {
for (const item of resultList) {
if (!item || CommonUtils.isEmpty(item.iconResource) ||
item.iconResource.indexOf(UPGRADE_ICONS_DIR) === CommonConstants.INVALID_VALUE) {
log.showDebug(`the icon resource path is abnormal, iconResource: ${item.iconResource}`);
continue;
}
count++;
await this.scaleIconUri(item.iconResource);
}
} catch (err) {
log.error('dealUpgradeIconsCompatibility err:', err);
}
sSettingsUtil.setValueEx(settings.domainName.USER_PROPERTY, UPGRADE_ICONS_COMPATIBILITY, CompatibilityType.COMPLETED, context);
log.showWarn(`upgrade icon compatibility processing completed, length: ${resultList.length}, count: ${count}`);
}
* 将图标放大后原路径写回
*
* @param path 图标路径
* @returns
*/
public async scaleIconUri(iconUri: string): Promise<void> {
let pixelMap: image.PixelMap | null = null;
try {
pixelMap = await this.loadImageFromDisk(iconUri);
if (pixelMap) {
const size: image.Size = pixelMap.getImageInfoSync().size;
await pixelMap.scale(CommonConstants.MIGRATE_PLACEHOLDER_ICON_SCALE,
CommonConstants.MIGRATE_PLACEHOLDER_ICON_SCALE);
let region: image.Region = {
x: (CommonConstants.MIGRATE_PLACEHOLDER_ICON_SCALE - 1) * size.width / 2,
y: (CommonConstants.MIGRATE_PLACEHOLDER_ICON_SCALE - 1) * size.height / 2,
size: { height: size.height, width: size.width }
};
await pixelMap.crop(region);
await this.saveImage2Disk(iconUri, pixelMap);
return;
}
} catch (err) {
log.error('getIconScaleUri err', err);
} finally {
pixelMap?.release();
}
return;
}
* 设置快捷方式支持克隆的标价位,带上了该标记版本,支持应用内加桌的快捷方式克隆
*
*/
private setShortcutSupportCloneFlag(): void {
try {
let context = (GlobalContext.getInstance().getObject('desktopContext') as ctx.ServiceExtensionContext);
const preference: preferences.Preferences =
preferences.getPreferencesSync(context, { name: 'DESKTOP_LAYOUT_INFO' });
preference.putSync(CommonConstants.SHORTCUT_CLONE, CommonConstants.SHORTCUT_CLONE_FLAG);
preference.flushSync();
log.showWarn(`setShortcutSupportCloneFlag success`);
} catch (error) {
log.showError(`setShortcutSupportCloneFlag failed ${error}`, );
}
}
private async queryCloneIconsUri(): Promise<AppItemInfo[]> {
const resultList: AppItemInfo[] = [];
let resultSet: rdb.ResultSet | undefined = undefined;
try {
const predicates = new rdb.RdbPredicates(RdbStoreConfig.gridLayoutInfo.tableName);
predicates.equalTo(GridLayoutInfoColumns.APP_STATUS, AppStatus.WAIT_FOR_HARMONY);
predicates.and().equalTo(GridLayoutInfoColumns.TYPE_ID, CommonConstants.TYPE_APP);
resultSet =
await rdbStoreHelper.query(predicates, [GridLayoutInfoColumns.ICON_RESOURCE, GridLayoutInfoColumns.INTENT]);
while (resultSet && resultSet.goToNextRow()) {
let appItemInfo = new AppItemInfo();
appItemInfo.iconResource = resultSet.getString(resultSet.getColumnIndex(GridLayoutInfoColumns.ICON_RESOURCE));
appItemInfo.intent = resultSet.getString(resultSet.getColumnIndex(GridLayoutInfoColumns.INTENT));
resultList.push(appItemInfo);
}
} catch (err) {
log.error('queryCloneIconsUri err :', err);
} finally {
resultSet?.close();
resultSet = undefined;
}
return resultList;
}
* 获取放大后的图标URI
*
* @param path 图标路径
* @returns 图标URI
*/
public async getIconScaleUri(iconUri: string): Promise<string> {
let pixelMap: image.PixelMap | undefined = undefined;
try {
pixelMap = await this.loadImageFromDisk(iconUri);
if (pixelMap) {
const size: image.Size = pixelMap.getImageInfoSync().size;
await pixelMap.scale(CommonConstants.MIGRATE_PLACEHOLDER_ICON_SCALE,
CommonConstants.MIGRATE_PLACEHOLDER_ICON_SCALE);
let region: image.Region = {
x: (CommonConstants.MIGRATE_PLACEHOLDER_ICON_SCALE - 1) * size.width / 2,
y: (CommonConstants.MIGRATE_PLACEHOLDER_ICON_SCALE - 1) * size.height / 2,
size: { height: size.height, width: size.width }
};
await pixelMap.crop(region);
let srcUriObject = new fileuri.FileUri(iconUri);
let dstPath: string = this.filesDir + ICONS_DIR + srcUriObject.name;
await this.saveImage2Disk(dstPath, pixelMap);
let dstUriObject = new fileuri.FileUri(dstPath);
let nameIndex: number = iconUri.lastIndexOf('/');
let pixelMapName: string = iconUri.substring(nameIndex + 1);
PixelMapUtil.addName(pixelMap, CommonConstants.SOURCE_CLONE + '_' + pixelMapName);
return dstUriObject.toString();
}
} catch (err) {
log.error('getIconScaleUri err', err);
} finally {
pixelMap?.release();
}
return '';
}
* 快捷方式图标转base64
*
* @param iconUri 图标路径
* @returns string base64图标资源
*/
public async getShortcutIcon(iconUri: string): Promise<string> {
let pixelMap: image.PixelMap | undefined = undefined;
try {
pixelMap = await this.loadImageFromDisk(iconUri);
if (pixelMap) {
return await GraphicUtils.changePixelToBase64(pixelMap);
}
} catch (err) {
log.error('getIconScaleUri err', err);
} finally {
pixelMap?.release();
}
return '';
}
* 根据路径加载图片
*
* @param path 图片路径
* @param format 格式
* @returns 图片资源
*/
private async loadImageFromDisk(path: string,
format: image.PixelMapFormat = image.PixelMapFormat.RGBA_8888): Promise<image.PixelMap | undefined> {
if (!path || !fs.access(path)) {
log.showWarn('fail to access path');
return undefined;
}
let fd: number = -1;
try {
fd = fs.openSync(path, fs.OpenMode.READ_ONLY).fd;
const imageSource: image.ImageSource = image.createImageSource(fd);
const imageItem = await imageSource.createPixelMap({
desiredPixelFormat: format,
});
await imageSource.release();
return imageItem;
} catch (e) {
log.error('loadImageFromDisk error', e);
} finally {
if (fd !== -1) {
fs.closeSync(fd);
}
}
return undefined;
}
* 将图标存入硬盘
*
* @param fullPath 图标路径
* @param pixelMap 图标pixelMap
*/
public async saveImage2Disk(fullPath: string, pixelMap: image.PixelMap): Promise<void> {
let file: Nullable<fs.File> | undefined;
try {
const imagePacker = image.createImagePacker();
const imageBuffer = await imagePacker.packing(pixelMap, {
format: 'image/png',
quality: 100,
});
imagePacker.release();
const mode = fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE;
file = await fs.open(fullPath, mode);
await fs.truncate(file.fd);
await fs.write(file.fd, imageBuffer);
} catch (e) {
log.error('Save image failed', e);
} finally {
if (file) {
try {
await fs.close(file);
} catch (e) {
log.error('Failed to close file', e);
}
}
}
}
}
* 兼容性类型
*
* @since 2025-01-09
*/
export enum CompatibilityType {
* 未处理过兼容性
*/
NONE = '0',
* 已处理过兼容性
*/
COMPLETED = '1',
}