/*
* 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 { ArrayUtils, LogDomain, LogHelper } from '@ohos/basicutils';
import {
NotificationBaseVm,
INotificationDataVm,
NotificationRefreshType,
NotificationRefreshRecord
} from '@ohos/systemuicommon/newIndex';
import {
InnerEventUtil,
LiveType,
NormalNotification,
NormalNotificationGroup,
NotificationBase,
NotificationCategory,
NotificationEvent
} from '@ohos/systemuicommon/newTsIndex';
const TAG = 'NotificationDataVm';
const log = LogHelper.getLogHelper(LogDomain.NC, TAG);
// 数据刷新记录仪
interface InnerRecord extends NotificationRefreshRecord {
// 是否开启数据记录
isRecordEnable: boolean;
}
/**
* 通知全数据处理业务
*/
export abstract class NotificationDataVm extends NotificationBaseVm implements INotificationDataVm {
/**
* 业务使用场景名
*/
protected readonly vmName: string = 'default';
/**
* 所有通知数据集,分组key
* <groupKey, NotificationBase>
*/
private readonly ntfGroupMap: Map<string, NotificationBase> = new Map();
/**
* 所有通知数据集,hashcode key
* <hashcode, NotificationBase>
*/
private readonly ntfHashMap: Map<string, NotificationBase> = new Map();
/**
* 数据刷新记录仪
*/
private readonly dataRefreshRecord: InnerRecord = {
isRecordEnable: false,
isDataRefresh: false,
dataRefreshType: NotificationRefreshType.DATA_INIT,
changeDataSet: new Set(),
updateDataSet: new Set()
};
/**
* 处理通知事件
* @param event 事件
*/
private onNotificationEvent = (event: NotificationEvent): void => {
this.handleNotificationEvent(event);
};
/**
* 初始化
*/
init(): void {
log.showInfo(`Init: ${this.vmName}`);
InnerEventUtil.on(NotificationEvent, this.onNotificationEvent);
}
/**
* 销毁
*/
destroy(): void {
log.showInfo(`Destroy: ${this.vmName}`);
InnerEventUtil.off(NotificationEvent, this.onNotificationEvent);
// 清空数据
this.ntfGroupMap.clear();
this.ntfHashMap.clear();
}
/**
* 分组数据集遍历
* @param callback 遍历回调
*/
groupForEach(callback: (value: NotificationBase, key: string, map: Map<string, NotificationBase>) => void): void {
this.ntfGroupMap.forEach(callback);
}
/**
* 取值分组数据
* @param groupKey 分组标识
* @returns 分组数据
*/
getNtfByGroupKey(groupKey: string): NotificationBase | undefined {
return this.ntfGroupMap.get(groupKey);
}
/**
* 取值单数据
* @param hashcode 单通知标识
* @returns 单数据
*/
getNtfByHashCode(hashcode: string): NotificationBase | undefined {
return this.ntfHashMap.get(hashcode);
}
/**
* 取值组通知数据
* @param ntf 原数据
* @returns 组通知数据
*/
getGroupNtf(ntf: NotificationBase): NormalNotificationGroup | undefined {
const existNtf = this.ntfGroupMap.get(ntf?.groupKey);
return existNtf?.isNormalGroup() ? existNtf : undefined;
}
/**
* 解除组通知
* @param group 组通知数据
*/
releaseGroup(group: NormalNotificationGroup): void {
log.showInfo(`Release group for ${group?.groupKey}`);
if (group) {
this.delMapData(group);
this.saveNtf(group.children?.[0]);
this.vmInjector.deleteVmByNtf(group);
}
}
/**
* 删除剩单条通知时,是否解除组通知
* @returns true解除组通知
*/
protected abstract isResetGroup(): boolean;
/**
* 数据刷新处理
* @param dataRecord 数据刷新记录
*/
protected onDataChange(dataRecord: NotificationRefreshRecord): void {
}
/**
* 通知数据事件处理
* 函数才能子类复写调用super
* @param event 事件
*/
protected handleNotificationEvent(event: NotificationEvent): void {
log.showInfo(`handleNotificationEvent, type: ${event.eventType}, size: ${event.notificationList?.length}`);
if (!event.notificationList?.length && event.eventType !== NotificationEvent.EVENT_TYPE_INIT) {
return;
}
const ntfList = this.handleExcludeNtf(event.notificationList);
// 开启数据刷新记录
this.startRecordDataRefresh();
if (event.eventType === NotificationEvent.EVENT_TYPE_INIT) {
this.handleInitEvent(ntfList);
// 标记初始化数据
this.recordRefreshType(NotificationRefreshType.DATA_INIT);
} else if (event.eventType === NotificationEvent.EVENT_TYPE_ADD) {
this.handleAddUpdateEvent(ntfList);
// 标记新增数据
this.recordRefreshType(NotificationRefreshType.DATA_ADD);
} else if (event.eventType === NotificationEvent.EVENT_TYPE_UPDATE) {
this.handleAddUpdateEvent(ntfList);
// 标记更新数据
this.recordRefreshType(NotificationRefreshType.DATA_UPDATE);
} else if (event.eventType === NotificationEvent.EVENT_TYPE_REMOVE) {
this.handleRemoveEvent(ntfList);
// 标记删除数据
this.recordRefreshType(NotificationRefreshType.DATA_DELETE);
}
// 关闭数据刷新记录
let recordResult = this.endRecordDataRefresh();
// 数据刷新回调
this.onDataChange(recordResult);
}
/**
* 过滤不支持在通知面板显示的ntf
* @param ntfList
* @returns
*/
protected handleExcludeNtf(ntfList: NotificationBase[]): NotificationBase[] {
return ntfList.filter(ntf => {
// 即时类实况不显示
if (this.isExcludedNtf(ntf)) {
return false;
}
return true;
})
}
/**
* 是否需要忽略不处理
* @param ntf
* @returns
*/
protected isExcludedNtf(ntf: NotificationBase): boolean {
// 即时类实况不显示
return ntf.isLiveView() && (ntf.type === LiveType.INSTANT || ntf.type === LiveType.INSTANT_BANNER);
}
/**
* 通知列表初始化
* @param incomingNtfList 初始化通知列表
*/
protected handleInitEvent(incomingNtfList: NotificationBase[]): void {
this.ntfGroupMap.clear();
this.ntfHashMap.clear();
this.handleAddUpdateEvent(incomingNtfList);
}
/**
* 通知列表添加或更新数据
* @param incomingNtfList 添加/更新集
*/
protected handleAddUpdateEvent(incomingNtfList: NotificationBase[]): void {
incomingNtfList?.forEach((incomingNtf) => {
this.deleteWrongGroup(incomingNtf);
let newData = this.saveNtf(incomingNtf);
// 记录新增数据
if (newData) {
this.recordDataArr(newData);
}
});
}
/**
* 通知列表删除数据
* @param incomingNtfList 删除集
*/
protected handleRemoveEvent(incomingNtfList: NotificationBase[]): void {
incomingNtfList?.forEach((incomingNtf) => {
let delData = this.clearNtf(incomingNtf);
// 记录删除数据
if (delData) {
this.recordDataArr(delData);
}
});
}
/**
* 如果通知的分组信息变了,需要将其从原位置删除
* @param incomingNtf 新来通知
*/
protected deleteWrongGroup(incomingNtf: NotificationBase): void {
const existNtf = this.ntfHashMap.get(incomingNtf?.hashCode);
// 不存在相同ID的通知,无需关注分组信息
if (!existNtf) {
return;
}
// 非组通知,分组信息变化或紧急类型通知,则直接清除旧数据
const existGroupNtf = this.getGroupNtf(existNtf);
if (!existGroupNtf) {
if (incomingNtf.groupKey !== existNtf.groupKey || incomingNtf.category === NotificationCategory.EMERGENCY) {
log.showInfo(`deleteWrongGroup ${incomingNtf.groupKey}, ${incomingNtf.category}`);
this.delMapData(existNtf);
// 实况复用view、vm
if (!existNtf.isLiveView()) {
this.vmInjector.deleteVmByNtf(existNtf);
}
}
} else {
// 组通知,分组信息变化,则清除旧数据
if (incomingNtf.groupKey === existNtf.groupKey) {
return;
}
log.showInfo(`deleteWrongGroup from ${existNtf.groupKey} to ${incomingNtf.groupKey}`);
this.delHashMapData(existNtf.hashCode);
const newGroup = existGroupNtf.removeChild(existNtf.hashCode);
// 删除数据后无法成组
if (!newGroup) {
this.delMapData(existGroupNtf);
this.vmInjector.deleteVmByNtf(existNtf);
this.vmInjector.deleteVmByNtf(existGroupNtf);
} else if (newGroup.children.length === 1) {
this.releaseGroup(newGroup);
} else {
this.addMapData(newGroup);
this.vmInjector.updateVmByNtf(newGroup);
}
}
}
/**
* 保存通知
* @param incomingNtf 新来通知
* @returns true新成组通知
*/
protected saveNtf(incomingNtf: NotificationBase): NotificationBase | undefined {
if (!incomingNtf) {
return undefined;
}
let addData: NotificationBase | undefined = undefined;
const existNtf = this.ntfGroupMap.get(incomingNtf.groupKey);
log.showInfo(`Save ntf for ${incomingNtf.hashCode}, existNtf groupKey: ${incomingNtf.groupKey}`);
if (existNtf) {
// 已经有组通知,则将其加入到组通知
if (existNtf?.isNormalGroup()) {
log.showInfo(`Add ntf ${incomingNtf.hashCode} to group ${existNtf.groupKey}`);
this.addHashMapData(incomingNtf);
const group = existNtf.addOrUpdateChild(incomingNtf as NormalNotification);
// 更新组通知的VM
this.vmInjector.updateVmByNtf(group);
this.addMapData(group);
// 记录数据刷新
this.recordUpdateDataArr(group);
this.recordDataRefresh(true);
} else if (existNtf.hashCode === incomingNtf.hashCode) {
log.showInfo(`Update single ntf for ${incomingNtf.hashCode}`);
// 两个通知hashCode一样,则替换它
this.addHashMapData(incomingNtf);
this.addMapData(incomingNtf);
// 记录数据刷新
this.recordUpdateDataArr(incomingNtf);
this.recordDataRefresh(true);
} else if (existNtf.isNormal() && incomingNtf.isNormal()) {
log.showInfo(`Merge ntf ${existNtf.hashCode} and ${incomingNtf.hashCode} to group ${incomingNtf.groupKey}`);
// 两个通知hashCode不一样但groupKey一样就组成一个组
const group = new NormalNotificationGroup(existNtf, incomingNtf);
group.updateKey(undefined, true);
this.addHashMapData(incomingNtf);
this.addMapData(group);
// 记录数据刷新
this.recordUpdateDataArr(group);
this.recordDataRefresh(true);
this.vmInjector.updateVmByNtf(group);
// 这里还需要子通知的更新isInGroup
}
} else {
// 新增单组通知
this.addHashMapData(incomingNtf);
this.addMapData(incomingNtf);
addData = incomingNtf;
// 记录数据刷新
this.recordDataRefresh(true);
}
// 更新通知的VM
this.vmInjector.updateVmByNtf(incomingNtf);
return addData;
}
/**
* 清除通知数据
* @param incomingNtf 删除数据
* @returns 被清除组通知
*/
protected clearNtf(incomingNtf: NotificationBase): NotificationBase | undefined {
if (!incomingNtf) {
return undefined;
}
let clearData: NotificationBase | undefined = undefined;
const existNtf = this.ntfGroupMap.get(incomingNtf.groupKey);
log.showInfo(`Delete ntf ${incomingNtf.hashCode}, existNtf groupKey: ${incomingNtf.groupKey}`);
if (existNtf?.isNormalGroup()) {
this.delHashMapData(incomingNtf.hashCode);
const newGroup = existNtf.removeChild(incomingNtf.hashCode);
if (!newGroup) {
log.showInfo(`Delete group ntf: ${incomingNtf.groupKey}`);
// 组通知被清除
clearData = existNtf;
this.delMapData(existNtf);
this.vmInjector.deleteVmByNtf(existNtf);
// 记录数据刷新
this.recordDataRefresh(true);
} else if (newGroup.children.length === 1 && this.isResetGroup()) {
// 只有一条通知且面板为收起状态则解除组通知
this.releaseGroup(newGroup);
// 记录数据刷新
this.recordUpdateDataArr(newGroup);
this.recordDataRefresh(true);
} else {
log.showInfo(`Update group ntf: ${newGroup.groupKey}`);
this.addMapData(newGroup);
this.vmInjector.updateVmByNtf(newGroup);
// 记录数据刷新
this.recordUpdateDataArr(newGroup);
this.recordDataRefresh(true);
}
} else if (existNtf?.hashCode === incomingNtf.hashCode) {
log.showInfo(`Delete single ntf: ${incomingNtf.hashCode}`);
clearData = existNtf;
this.delMapData(incomingNtf);
// 记录数据刷新
this.recordDataRefresh(true);
}
this.vmInjector.deleteVmByNtf(incomingNtf);
return clearData;
}
/**
* 开启数据刷新记录
*/
private startRecordDataRefresh(): void {
this.resetRecordDataRefresh();
this.dataRefreshRecord.isRecordEnable = true;
}
/**
* 结束数据刷新记录
* @returns 最终记录结果
*/
private endRecordDataRefresh(): NotificationRefreshRecord {
if (this.dataRefreshRecord.isRecordEnable) {
// 最终记录结果进行转换
let changeSet: Set<string> = new Set();
this.dataRefreshRecord.changeDataSet?.forEach((groupKey) => {
changeSet.add(groupKey);
// 新增/删除数据,按非更新处理
this.dataRefreshRecord.updateDataSet?.delete(groupKey);
});
let updateSet: Set<string> = new Set();
this.dataRefreshRecord.updateDataSet?.forEach((groupKey) => updateSet.add(groupKey));
let result: NotificationRefreshRecord = {
isDataRefresh: this.dataRefreshRecord.isDataRefresh,
dataRefreshType: this.dataRefreshRecord.dataRefreshType,
changeDataSet: changeSet,
updateDataSet: updateSet
};
// 重置记录仪
this.resetRecordDataRefresh();
return result;
}
this.resetRecordDataRefresh();
return { isDataRefresh: false, dataRefreshType: NotificationRefreshType.DATA_INIT };
}
/**
* 数据刷新记录复位
*/
private resetRecordDataRefresh(): void {
this.dataRefreshRecord.isRecordEnable = false;
this.dataRefreshRecord.isDataRefresh = false;
this.dataRefreshRecord.dataRefreshType = NotificationRefreshType.DATA_INIT;
this.dataRefreshRecord.changeDataSet?.clear();
this.dataRefreshRecord.updateDataSet?.clear();
}
/**
* 记录数据刷新状态
* @param isRefresh true数据刷新
*/
private recordDataRefresh(isRefresh: boolean): void {
// 开启记录时,允许刷新状态
if (this.dataRefreshRecord.isRecordEnable) {
this.dataRefreshRecord.isDataRefresh = isRefresh;
}
}
/**
* 记录新增/删除数据
* @param ntf 变化数据
*/
private recordDataArr(ntf: NotificationBase): void {
// 开启记录时,允许记录变化数据
if (this.dataRefreshRecord.isRecordEnable) {
this.dataRefreshRecord.changeDataSet?.add(ntf?.groupKey);
}
}
/**
* 记录更新数据
* @param ntf 变化数据
*/
private recordUpdateDataArr(ntf: NotificationBase): void {
// 开启记录时,允许记录变化数据
if (this.dataRefreshRecord.isRecordEnable) {
this.dataRefreshRecord.updateDataSet?.add(ntf?.groupKey);
}
}
/**
* 记录数据刷新类型
* @param refreshType 刷新类型
*/
private recordRefreshType(refreshType: NotificationRefreshType): void {
// 初始化场景,直接记录
if (refreshType === NotificationRefreshType.DATA_INIT) {
this.dataRefreshRecord.dataRefreshType = refreshType;
return;
}
// 新增、更新场景,有数据则新增,无数据则更新
if (refreshType === NotificationRefreshType.DATA_ADD || refreshType === NotificationRefreshType.DATA_UPDATE) {
let resultType = ArrayUtils.isEmpty(this.dataRefreshRecord.changeDataSet) ?
NotificationRefreshType.DATA_UPDATE : NotificationRefreshType.DATA_ADD;
this.dataRefreshRecord.dataRefreshType = resultType;
return;
}
// 删除场景,有数据则删除,无数据则更新
let resultType = ArrayUtils.isEmpty(this.dataRefreshRecord.changeDataSet) ?
NotificationRefreshType.DATA_UPDATE : NotificationRefreshType.DATA_DELETE;
this.dataRefreshRecord.dataRefreshType = resultType;
}
/**
* 缓存数据
* @param group 数据
*/
private addMapData(group: NotificationBase): void {
if (group) {
this.ntfGroupMap.set(group.groupKey, group);
}
}
/**
* 缓存hashcode标识数据
* @param single 数据
*/
protected addHashMapData(single: NotificationBase): void {
if (single) {
this.ntfHashMap.set(single.hashCode, single);
}
}
/**
* 清除已有数据
* @param existNtf 已有数据
* @returns true删除成功
*/
private delMapData(existNtf: NotificationBase): boolean {
if (!existNtf) {
return false;
}
if (existNtf.isNormalGroup()) {
existNtf.children?.forEach((child) => this.delHashMapData(child?.hashCode));
} else {
this.delHashMapData(existNtf.hashCode);
}
return this.ntfGroupMap.delete(existNtf.groupKey);
}
/**
* 清除hashcode标识数据
* @param hashcode 标识
* @returns true删除成功
*/
protected delHashMapData(hashcode: string): boolean {
return this.ntfHashMap.delete(hashcode);
}
}