* 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, Logger } from '@ohos/basicutils';
import { DockItemInfo } from '../../bean/DockItemInfo';
import GridLayoutItemInfo from '../../bean/GridLayoutItemInfo';
import { LaunchLayoutCacheManager } from '../../cache/layout/LaunchLayoutCacheManager';
import { ResidentLayoutCacheMgr } from '../../dock/cache/ResidentLayoutCacheMgr';
import GridLayoutUtil from '../../utils/GridLayoutUtil';
import { IconChangeListener } from '../AppModel';
import { ArrayUtils } from '@ohos/basicutils';
import { CommonConstants } from '../../constants/CommonConstants';
import { FolderAppItemInfo } from '../../folder/FolderItemInfo';
import { desktopUtil } from '@ohos/componenthelper';
import IconInfo from '@ohos/frameworkwrapper/src/main/ets/resourcemanager/IconInfo';
import { IconExtendParam, TaskInfo } from '@ohos/frameworkwrapper';
import { SCBConstants } from '@ohos/commonconstants';
import { LauncherAnimUtil } from '../../utils/LauncherAnimUtil';
import { FolderManager } from '../../folder/next/common/model/FolderManager';
const BATCH_COUNT: number = 12;
const REFRESH_INTERVAL: number = 100;
const MAX_BREAK_COUNT: number = 3;
const BREAK_DELAY: number = 600;
const TAG = 'LayoutRefreshManager';
const log: Logger = Logger.getLogHelper(LogDomain.HOME);
* 控件刷新通知类,用于按不同有优先级刷新布局(优先级逻辑和被交互打断场景待补充)
*/
export class LayoutRefreshManager {
private static instance?: LayoutRefreshManager;
private timeOutId: number = -1;
private isCancelRefresh: boolean = false;
private breakCount: number = 0;
private constructor() {
}
public static getInstance(): LayoutRefreshManager {
if (!LayoutRefreshManager.instance) {
LayoutRefreshManager.instance = new LayoutRefreshManager();
}
return LayoutRefreshManager.instance;
}
* 将桌面上所有图标控件按当前显示页进行排序,不显示在桌面上的图标排在最后
*
* @param deliverAppIconInfosMap dh相关应用列表
* @returns 排序好的图标集合,当前页上的图标显示在最前面,其次是相邻页,最后是显示在文件夹内的图标
*/
public sortLayout(deliverAppIconInfosMap: Map<string, IconInfo>): RefreshViewDataCollection {
log.showWarn(TAG, 'sortLayout start');
let launchAppInfos: GridLayoutItemInfo[] = LaunchLayoutCacheManager.getInstance().getGridLayoutItemList();
let showLaunchAppInfos: RefreshViewData[] = [];
FolderManager.getInstance();
launchAppInfos.filter((item: GridLayoutItemInfo) => {
return item.typeId === CommonConstants.TYPE_APP;
}).forEach((item: GridLayoutItemInfo) => {
showLaunchAppInfos.push(new RefreshViewData(item));
});
let pageIndex: number = desktopUtil.getPageIndexValue();
let dockLayoutItemList: DockItemInfo[] = ResidentLayoutCacheMgr.getInstance().getAllDockItems();
dockLayoutItemList?.forEach((dockItem: DockItemInfo) => {
let gridItemInfo: GridLayoutItemInfo = GridLayoutUtil.dockItemToGridLayout(dockItem);
if (gridItemInfo.typeId === CommonConstants.TYPE_APP) {
showLaunchAppInfos.push(new RefreshViewData(gridItemInfo, pageIndex));
}
});
let collection: RefreshViewDataCollection = new RefreshViewDataCollection();
collection.currentViews = showLaunchAppInfos.filter((data: RefreshViewData) => {
return data.pageIndex === pageIndex;
}).map<TaskInfo>((data: RefreshViewData) => {
return this.dataToTaskInfo(data, deliverAppIconInfosMap);
});
log.showWarn(TAG, 'sortLayout currentViews size: %{public}d', collection.currentViews.length);
collection.otherViews = showLaunchAppInfos.filter((data: RefreshViewData) => {
return data.pageIndex !== pageIndex;
}).sort((dataA: RefreshViewData, dataB: RefreshViewData) => {
return Math.abs(dataA.pageIndex - pageIndex) - Math.abs(dataB.pageIndex - pageIndex);
}).map<TaskInfo>((data: RefreshViewData) => {
return this.dataToTaskInfo(data, deliverAppIconInfosMap);
});
log.showWarn(TAG, 'sortLayout otherViews size: %{public}d', collection.otherViews.length);
let backgroundViews: TaskInfo[] = [];
let allInfo: GridLayoutItemInfo[] = LaunchLayoutCacheManager.getInstance().getAllApps();
allInfo.filter((item: GridLayoutItemInfo) => {
return item.typeId === CommonConstants.TYPE_APP && !collection.getShowBundleSet().has(item.bundleName);
}).forEach((item: GridLayoutItemInfo) => {
backgroundViews.push(this.dataToTaskInfo(new RefreshViewData(item), deliverAppIconInfosMap));
});
collection.backgroundViews = backgroundViews;
log.showWarn(TAG, 'sortLayout backgroundViews size: %{public}d', collection.backgroundViews.length);
return collection;
}
private dataToTaskInfo(data: RefreshViewData, deliverAppIconInfosMap: Map<string, IconInfo>): TaskInfo {
let param = new IconExtendParam();
param.bundleName = deliverAppIconInfosMap.has(data.bundleName) ?
data.bundleName + SCBConstants.BUNDLENAME_APPEND_TEMPLATE : data.bundleName;
return new TaskInfo(data.bundleName, data.moduleName, data.abilityName, param);
}
* 批量刷新图标控件
*
* @param iconChangeListener 图标控件刷新回调
* @param taskInfos 需要刷新的图标集合
*/
public refreshViewBatch(iconChangeListener: IconChangeListener[], taskInfos: TaskInfo[]): void {
if (ArrayUtils.isEmpty(iconChangeListener) || ArrayUtils.isEmpty(taskInfos)) {
log.showError(TAG, 'listener array is null in refreshView');
return;
}
log.showWarn(TAG, 'refreshView start, listener size: %{public}d', iconChangeListener.length);
taskInfos.forEach((info: TaskInfo) => {
iconChangeListener.filter((listener: IconChangeListener) => {
return listener.bundleName === info.bundleName && listener.moduleName === info.moduleName &&
listener.abilityName === info.abilityName;
}).forEach((listener: IconChangeListener) => {
listener.onIconResourceChange();
});
});
}
* 通知图标控件批量完成UI刷新
*
* @param iconChangeListener 控件刷新监听
* @param collection 控件刷新优先级标识
* @param allFinished 刷新完成回调
*/
public refreshView(iconChangeListener: IconChangeListener[], collection: RefreshViewDataCollection,
allFinished: () => void): void {
if (ArrayUtils.isEmpty(iconChangeListener)) {
log.showError(TAG, 'listener array is null in refreshView');
allFinished();
return;
}
this.isCancelRefresh = false;
this.breakCount = 0;
let currentViews: TaskInfo[] = [];
if (!ArrayUtils.isEmpty(collection.currentViews)) {
currentViews = collection.getTaskInfoAndDelete(BATCH_COUNT);
}
currentViews.forEach((task: TaskInfo) => {
iconChangeListener.filter((listener: IconChangeListener) => {
return listener.bundleName === task.bundleName && listener.moduleName === task.moduleName &&
listener.abilityName === task.abilityName;
}).forEach((listener: IconChangeListener) => {
listener.onIconResourceChange();
});
});
log.showWarn(TAG, 'refreshView start, listener size: %{public}d, currentView size:', iconChangeListener.length,
currentViews.length);
this.batchRefreshView(collection, iconChangeListener, allFinished);
}
private batchRefreshView(collection: RefreshViewDataCollection, iconChangeListener: IconChangeListener[],
allFinished: () => void): void {
if (LauncherAnimUtil.isUiThreadBusy() && this.breakCount < MAX_BREAK_COUNT) {
log.showWarn(TAG, 'batchRefreshView by break, breakCount: %{public}d', this.breakCount);
this.breakCount++;
this.timeOutId = setTimeout(() => {
this.batchRefreshView(collection, iconChangeListener, allFinished);
}, BREAK_DELAY);
return;
}
let tasks: TaskInfo[] = collection.getTaskInfoAndDelete(BATCH_COUNT);
if (tasks.length === 0) {
log.showWarn(TAG, 'batchRefreshView end');
allFinished();
return;
}
log.showWarn(TAG, 'batchRefreshView start, refresh: %{public}d', tasks.length);
tasks.forEach((task: TaskInfo) => {
iconChangeListener.filter((listener: IconChangeListener) => {
return listener.bundleName === task.bundleName && listener.moduleName === task.moduleName &&
listener.abilityName === task.abilityName;
}).forEach((listener: IconChangeListener) => {
listener.onIconResourceChange();
});
});
if (this.isCancelRefresh) {
return;
}
this.timeOutId = setTimeout(() => {
this.batchRefreshView(collection, iconChangeListener, allFinished);
}, REFRESH_INTERVAL);
}
* 如当前正在进行分批刷新则取消当前的刷新
*/
public cancelRefresh(): void {
log.showWarn(TAG, 'cancelRefresh, timeOutId: %{public}d', this.timeOutId);
this.isCancelRefresh = true;
if (this.timeOutId !== -1) {
clearTimeout(this.timeOutId);
}
}
}
export class RefreshViewData {
bundleName: string = '';
moduleName: string = '';
abilityName: string = '';
pageIndex: number = 0;
constructor(item: GridLayoutItemInfo | FolderAppItemInfo, page?: number) {
this.bundleName = item.bundleName;
this.moduleName = item.moduleName ?? '';
this.abilityName = item.abilityName;
this.pageIndex = (page === undefined ? item.page : page) ?? 0;
}
}
export class RefreshViewDataCollection {
currentViews: TaskInfo[] = [];
otherViews: TaskInfo[] = [];
backgroundViews: TaskInfo[] = [];
extraViews: TaskInfo[] = [];
constructor() {
this.currentViews = [];
this.otherViews = [];
this.backgroundViews = [];
this.extraViews = [];
}
* 获取显示在桌面上图标控件的包名集合
* PS:桌面支持单应用多图标后此处需要变更
*
* @returns 显示桌面上的图标(不包含快捷方式)包名集合
*/
public getShowBundleSet(): Set<string> {
let set: Set<string> = new Set();
this.currentViews.forEach((data: TaskInfo) => {
set.add(data.bundleName);
});
this.otherViews.forEach((data: TaskInfo) => {
set.add(data.bundleName);
});
return set;
}
* 按刷新优先级从集合中获取需要刷新的控件
*
* @param batchCount 每批获取刷新控件的个数
* @returns 需要刷新的控件
*/
public getTaskInfoAndDelete(batchCount: number): TaskInfo[] {
let tasks: TaskInfo[] = [];
if (!ArrayUtils.isEmpty(this.currentViews)) {
tasks = this.currentViews.splice(0, this.currentViews.length);
} else if (!ArrayUtils.isEmpty(this.otherViews)) {
tasks = this.otherViews.splice(0, this.otherViews.length >= batchCount ? batchCount : this.otherViews.length);
} else if (!ArrayUtils.isEmpty(this.backgroundViews)) {
tasks = this.backgroundViews.splice(0,
this.backgroundViews.length >= batchCount ? batchCount : this.backgroundViews.length);
} else {
tasks = this.extraViews.splice(0,
this.extraViews.length >= batchCount ? batchCount : this.extraViews.length);
}
return tasks;
}
* 将非应用图标类型的控件信息填充入额外刷新图标中
*
* @param iconChangeListener 控件监听信息
*/
public fillingExtraView(iconChangeListener: IconChangeListener[]): void {
iconChangeListener.forEach((listener: IconChangeListener) => {
if (!listener.bundleName) {
return;
}
if (this.isExistView(this.currentViews, listener) || this.isExistView(this.otherViews, listener) ||
this.isExistView(this.backgroundViews, listener)) {
return;
}
let task: TaskInfo =
new TaskInfo(listener.bundleName, listener.moduleName, listener.abilityName, new IconExtendParam());
if (this.isExistView(this.extraViews, task)) {
return;
}
this.extraViews.push(task);
});
}
private isExistView(tasks: TaskInfo[], info: IconChangeListener | TaskInfo): boolean {
let index: number = tasks.findIndex((task: TaskInfo) => {
return task.bundleName === info.bundleName && task.moduleName === info.moduleName &&
task.abilityName === info.abilityName;
});
return index !== -1;
}
}