/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* 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 Constants from '../../common/constants/constant';
import { HiLog } from '../../common/base/HiLog';
import { common, Want } from '@kit.AbilityKit';
import ViewAbilityService from '../../rpc/ViewAbility/service/ViewAbilityService';
import { DecryptState, OpenDlpFileManager } from './OpenDlpFileManager';
import { FileParseType } from '../../bean/data/FileParseType';
import OpenDlpFileData from '../data/OpenDlpFileData';
import FileUtils from '../../common/FileUtils/FileUtils';
import ErrorManager from '../handler/ErrorHandler';
import { emitter } from '@kit.BasicServicesKit';
import StartSandboxHandler from '../handler/StartSandboxHandler';
import TerminateView from '../TerminateView/TerminateView';
import FileIdHandler from '../handler/FileIdHandler';
const TAG = 'OpeningDialogManager';
interface DecryptingState {
uri: string,
dialogTimeout: boolean, // “正在打开”动效是否超过250ms,拉“正在打开”动效之前状态设置为false,一直到超时回调才设置为true
needStartAbility: boolean, // 是否需要拉沙箱
hasDecrypted: boolean, // 是否解密完成,openDLPFile结束后设置为true
needShowDialog: boolean, // 是否需要拉起“正在打开”动效
isZip: boolean, // 是否是zip打包格式,用于zip格式,用户取消“正在打开”动效判断
needShowErr: boolean, // 是否需要处理错误弹框
}
export default class OpeningDialogManager {
private static instance: OpeningDialogManager;
private _showDialogState: boolean = false;// 用于表示“正在打开”动效是否正在展示
private _hasCallback: boolean = false;// SEA拉起UEA后,是否注册回调
private _isChargeDecrypting: boolean = false;// 是否正在处理解密逻辑
private _decryptingMap: Map<string, DecryptingState>;// 正在解密的requestId和解密过程的信息
private _isTerminalSelf: boolean = false;
private _isWaitingShowToast: boolean = false;
private _callbackTimeOut: number | null = null;
private checkShowDialogStateEvent = async () => {
HiLog.info(TAG, 'receive CHECK_SHOW_DIALOG_STATE');
this.checkShowDialogState();
};
private constructor() {
this._decryptingMap = new Map<string, DecryptingState>();
}
public registerEmitEvent() {
HiLog.debug(TAG, 'registerEmitEvent');
emitter.on(Constants.CHECK_SHOW_DIALOG_STATE, this.checkShowDialogStateEvent);
}
public unRegisterEmitEvent() {
HiLog.debug(TAG, 'unRegisterEmitEvent');
emitter.off(Constants.CHECK_SHOW_DIALOG_STATE, this.checkShowDialogStateEvent);
}
private printAllDecryptingMap(): void {
HiLog.debug(TAG, `printAllDecryptingMap ${this._decryptingMap.size}`);
this._decryptingMap.forEach((value, key) => {
HiLog.debug(TAG, `${
JSON.stringify({
requestId: key,
uri: FileUtils.getAnonymizedFilenameByUri(value.uri),
dialogTimeout: value.dialogTimeout,
needStartAbility: value.needStartAbility,
hasDecrypted: value.hasDecrypted,
needShowDialog: value.needShowDialog,
isZip: value.isZip,
needShowErr: value.needShowErr
})}`);
});
}
static getInstance(): OpeningDialogManager {
if (!OpeningDialogManager.instance) {
OpeningDialogManager.instance = new OpeningDialogManager();
}
return OpeningDialogManager.instance;
}
public setIsWaitingShowToast(value: boolean) {
this._isWaitingShowToast = value;
}
public getIsWaitingShowToast(): boolean {
return this._isWaitingShowToast;
}
private setShowDialogState(value: boolean) {
HiLog.debug(TAG, `setShowDialogState ${value}`);
this._showDialogState = value;
}
public getHasCallback(): boolean {
HiLog.debug(TAG, `getHasCallback ${this._hasCallback}`);
return this._hasCallback;
}
public setIsTerminalSelf(value: boolean) {
HiLog.debug(TAG, `setIsTerminalSelf ${value}`);
this._isTerminalSelf = value;
}
public getIsTerminalSelf(): boolean {
HiLog.debug(TAG, `getIsTerminalSelf ${this._isTerminalSelf}`);
return this._isTerminalSelf;
}
private setHasCallback(hasCallback: boolean): void {
HiLog.debug(TAG, `setHasCallback ${hasCallback}`);
this._hasCallback = hasCallback;
}
public getIsDecryptingByRequestId(requestId: string): boolean {
this.printAllDecryptingMap();
const isDecrypting = this._decryptingMap.has(requestId);
HiLog.info(TAG, `getIsDecryptingByRequestId requestId ${requestId} isDecrypting ${isDecrypting}`);
return isDecrypting;
}
public getIsDecrypting(): boolean {
this.printAllDecryptingMap();
const isDecrypting = this._decryptingMap.size > 0;
HiLog.info(TAG, `getIsDecrypting isDecrypting ${isDecrypting}`);
return isDecrypting;
}
public setIsChargeDecrypting(value: boolean) {
HiLog.info(TAG, `setIsChargeDecrypting ${value}`);
this._isChargeDecrypting = value;
}
public getIsChargeDecrypting(): boolean {
HiLog.info(TAG, `getIsChargeDecrypting ${this._isChargeDecrypting}`);
return this._isChargeDecrypting;
}
// 删除_decryptingMap中的加密信息
public deleteRequestId(requestId: string) {
this.printAllDecryptingMap();
HiLog.info(TAG, `deleteRequestId requestId ${requestId}`);
this._decryptingMap.delete(requestId);
}
// 判断是否需要错误处理,如果这次不需要,就要等callback或者timeout的回调
public getCanShowToast(requestId: string): boolean {
this.printAllDecryptingMap();
HiLog.info(TAG, `OpeningDialogManager getCanShowToast requestId ${requestId}`);
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (!decryptingState) {
HiLog.error(TAG, 'getCanShowToast not isDecrypting');
return true;
}
const hasCallback = this.getHasCallback();
HiLog.info(TAG, `getCanShowToast ${
JSON.stringify({
requestId,
needShowDialog: decryptingState.needShowDialog,
showDialogState: this._showDialogState,
hasCallback,
dialogTimeout: decryptingState.dialogTimeout,
})}`);
// 需要弹框,callback回来了,但是timeout没回来,意味着正在持续250ms,不能弹框
if (this._showDialogState && decryptingState.needShowDialog && hasCallback && !decryptingState.dialogTimeout) {
return false;
}
return true;
}
// 判断是否需要拉起沙箱,这次不需要就等timeout的回调或者callback的回调
public getNeedStartAbility(requestId: string): boolean {
this.printAllDecryptingMap();
HiLog.info(TAG, `OpeningDialogManager getNeedStartAbility requestId ${requestId}`);
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (!decryptingState) {
HiLog.error(TAG, 'getNeedStartAbility not isDecrypting');
return false;
}
const hasCallback = this.getHasCallback();
HiLog.info(TAG, `getNeedStartAbility ${
JSON.stringify({
requestId,
needShowDialog: decryptingState.needShowDialog,
showDialogState: this._showDialogState,
hasCallback,
dialogTimeout: decryptingState.dialogTimeout,
})}`);
// 因为动效不一致的问题,所以需要拉起“正在打开”动效的请求,都必须等待callback
if (decryptingState.needShowDialog && !hasCallback) {
return false;
}
return true;
}
// 判断是否可以拉起沙箱,即用户是否手动终止“正在打开”动效
public getCanStartAbility(requestId: string): boolean {
this.printAllDecryptingMap();
const isDecrypting = this._decryptingMap.has(requestId);
HiLog.info(TAG, `getCanStartAbility requestId ${requestId}, isDecrypting ${isDecrypting}`);
return isDecrypting;
}
// “正在打开”动效消失的回调,需要判断是否是用户主动终止解密流程
public async dialogDisappear(requestId: string): Promise<void> {
this.printAllDecryptingMap();
HiLog.info(TAG, `OpeningDialogManager dialogDisappear requestId ${requestId}`);
FileIdHandler.getInstance().dialogCancel(requestId);
this.setShowDialogState(false);
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (!decryptingState) {
HiLog.info(TAG, 'dialogDisappear uri is not decrypting');
return;
}
const hasDecrypted = decryptingState.hasDecrypted;
const isZip = decryptingState.isZip;
HiLog.info(TAG, `dialogDisappear hasDecrypted ${hasDecrypted} isZip ${isZip}`);
if (!hasDecrypted || (!hasDecrypted && isZip)) { // 如果正在解密且不需要拉弹框,是用户主动终止解密流程
HiLog.info(TAG, 'user close opening dialog');
await OpenDlpFileManager.getInstance().deleteStatus(decryptingState.uri);
this.deleteRequestId(requestId);
this.setIsChargeDecrypting(false);
this.printAllDecryptingMap();
}
}
// “正在打开”动效超过250ms的回调
public async dialogTimeout(requestId: string): Promise<void> {
this.printAllDecryptingMap();
HiLog.info(TAG, `OpeningDialogManager dialogTimeout requestId ${requestId} hasCallback ${this._hasCallback}`);
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (!decryptingState) {
HiLog.info(TAG, 'dialogTimeout uri is not decrypting');
await this.handleError(requestId);
return;
}
decryptingState.dialogTimeout = true;
this.printAllDecryptingMap();
if (decryptingState.needShowErr && this._hasCallback) {
// callback回来之后,发现未解密完成,需要拉起“正在打开”的动效,等待timeout之后,进行错误处理(错误处理,情况三)
await this.handleError(requestId);
}
if (decryptingState.needStartAbility && this._hasCallback) {
// callback回来之后,发现未解密完成,需要拉起“正在打开”的动效,等待timeout之后,拉起沙箱(拉起沙箱,情况三)
await this.startSandbox(requestId);
}
await TerminateView.terminate();
}
// 拉起沙箱的情况二和情况三
private async startSandbox(requestId: string) {
const startSandboxRet = await StartSandboxHandler.getInstance().startSandbox();
if (startSandboxRet.errcode !== Constants.ERR_CODE_SUCCESS) {
HiLog.error(TAG, 'OpeningDialogManager startSandbox error');
}
this.deleteRequestId(requestId);
}
// 错误处理的情况二和情况三
private async handleError(requestId: string) {
const handleErrorRet = await ErrorManager.getInstance().startHandleError(requestId);
if (handleErrorRet.errcode !== Constants.ERR_CODE_SUCCESS) {
HiLog.error(TAG, 'startHandleError error');
}
this.deleteRequestId(requestId);
}
private async checkNeedStartAbilityOrShowErr(): Promise<void> {
this.printAllDecryptingMap();
let requestId = '';
let needStartAbilityTemp = false;
let needShowErr = false;
for (const entry of this._decryptingMap) {
if (entry[1].needStartAbility && entry[1].hasDecrypted) {
HiLog.info(TAG, `needStartAbility and hasDecrypted ${entry[0]}`);
requestId = entry[0];
needStartAbilityTemp = true;
break;
}
if (entry[1].needShowErr) {
HiLog.info(TAG, `needShowErr and hasDecrypted ${entry[0]}`);
requestId = entry[0];
needShowErr = true;
break;
}
}
if (needStartAbilityTemp) {
// callback回来之后,发现解密完成,拉起沙箱(拉起沙箱,情况二)
await this.startSandbox(requestId);
}
if (needShowErr) {
// callback回来之后,发现解密完成,直接错误处理(错误处理,情况二)
await this.handleError(requestId);
}
}
// SEA向UEA发送打开“正在打开”动效
private async showDialog(requestId: string): Promise<void> {
const setRet: boolean = await ViewAbilityService.getInstance().showDialog(true, requestId);
this.setShowDialogState(setRet);
HiLog.info(TAG, `OpeningDialogManager showDialog requestId: ${requestId}, state: ${this._showDialogState}`);
}
// SEA向UEA发送取消“正在打开”动效
private async hideDialog(): Promise<void> {
HiLog.info(TAG, 'OpeningDialogManager hideDialog start');
const setRet: boolean = await ViewAbilityService.getInstance().showDialog(false);
if (!setRet) {
HiLog.debug(TAG, 'showDialog sendMsg failed, showDialogState false');
this.setShowDialogState(false);
}
}
// callback回调后,根据当前解密内容,判断是否需要拉起“正在打开”动效
private getCurrentDecryptingRequestId(): string | undefined {
this.printAllDecryptingMap();
for (const entry of this._decryptingMap) {
if (!entry[1].needStartAbility && !entry[1].hasDecrypted) {
HiLog.info(TAG, `getCurrentDecryptingRequestId found requestId ${entry[0]}`);
return entry[0];
}
}
HiLog.info(TAG, 'getCurrentDecryptingRequestId not found');
return undefined;
}
// callback的回调,表示已经收到UEA的回调了,即已经注册通信回调了,可以进行通信了
// 需要根据解密状态,校验是否需要拉起“正在打开”动效
public async checkShowDialogState(): Promise<void> {
this.printAllDecryptingMap();
this.setHasCallback(true);
const isDecrypting = this._decryptingMap.size > 0;
const requestId = this.getCurrentDecryptingRequestId();
HiLog.info(TAG, `OpeningDialogManager checkShowDialogState showDialogState ${this._showDialogState},
isDecrypting ${isDecrypting}, requestId ${requestId}, isChargeDecrypting ${this._isChargeDecrypting}`);
await this.checkNeedStartAbilityOrShowErr();
if (!isDecrypting && !this._isChargeDecrypting) { // 没在处理“正在打开”动效逻辑或解密逻辑,需要取消“正在打开”动效
await this.hideDialog();
await ViewAbilityService.getInstance().sendDisconnectMsg();
return;
}
if (!requestId) {
HiLog.info(TAG, 'OpeningDialogManager checkShowDialogState getDecryptingRequestId not found');
await this.hideDialog();
await ViewAbilityService.getInstance().sendDisconnectMsg();
return;
}
if (isDecrypting && !this._showDialogState) { // 正在解密,且没拉起“正在打开”动效,需要拉起“正在打开”动效
this.setShowDialogState(true);
await this.showDialog(requestId);
return;
}
}
// openDLPFile前调用,或者zip格式提前调用;设置正在解密的状态
public async showOpeningDialog(uri: string, requestId: string, needShowDialog: boolean,
isZip?: boolean): Promise<void> {
this.printAllDecryptingMap();
HiLog.info(TAG, 'OpeningDialogManager showOpeningDialog');
if (this._decryptingMap.has(requestId)) {
HiLog.info(TAG, 'OpeningDialogManager showOpeningDialog is decrypting');
return;
}
const decryptingState: DecryptingState = {
uri: uri,
dialogTimeout: false,
needStartAbility: false,
hasDecrypted: false,
needShowDialog: needShowDialog,
isZip: isZip ?? false,
needShowErr: false
}
this._decryptingMap.set(requestId, decryptingState);
this.printAllDecryptingMap();
if (this._showDialogState) {
HiLog.info(TAG, 'is showing OpeningDialog');
return;
}
if (!needShowDialog) {
HiLog.info(TAG, 'no need showDialog');
return;
}
await this.showDialog(requestId);
}
// 根据大小不需要“正在打开”动效,但是调用penDLPFile接口超过500ms都没返回,拉起“正在打开”动效
public async showOpeningDialogByTimeout(requestId: string): Promise<void> {
const context: common.ServiceExtensionContext | undefined = AppStorage.get('viewContext');
if (!context) {
HiLog.error(TAG, 'showOpeningDialogByTimeout viewContext null');
return;
}
const viewContext = context as common.ServiceExtensionContext;
this.printAllDecryptingMap();
HiLog.info(TAG, 'OpeningDialogManager showOpeningDialogByTimeout');
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (!decryptingState) {
HiLog.error(TAG, 'showOpeningDialogByTimeout but not isDecrypting');
return;
}
decryptingState.needShowDialog = true;
this.printAllDecryptingMap();
if (this._showDialogState) {
HiLog.info(TAG, 'showOpeningDialogByTimeout is showing OpeningDialog');
return;
}
await this.loadOpeningDialogUIExtAbility(viewContext, requestId);
await this.showDialog(requestId);
}
// 解密完成后(openDLPFile)不管解密成功还是失败,都会调用
public async setHasDecrypted(requestId: string): Promise<void> {
this.printAllDecryptingMap();
HiLog.info(TAG, `OpeningDialogManager setHasDecrypted requestId ${requestId}`);
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (!decryptingState) {
HiLog.info(TAG, 'setHasDecrypted uri is not decrypting');
return;
}
decryptingState.hasDecrypted = true;
this.printAllDecryptingMap();
HiLog.info(TAG, `setHasDecrypted showDialogState ${this._showDialogState}`);
if (!this._showDialogState) {
return;
}
await this.hideDialog();
}
// 解密成功,且安装沙箱成功之后,拉起沙箱之前调用
public setNeedStartAbility(requestId: string): void {
this.printAllDecryptingMap();
HiLog.info(TAG, `OpeningDialogManager setNeedStartAbility requestId ${requestId}`);
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (!decryptingState) {
HiLog.info(TAG, 'setNeedStartAbility uri is not decrypting');
return;
}
decryptingState.needStartAbility = true;
// 已经完成上一次解密请求了
this.setIsChargeDecrypting(false);
this.printAllDecryptingMap();
}
// 因为各种原因解密失败,需要取消“正在打开”动效
public async hideOpeningDialogByFailed(requestId: string | undefined): Promise<void> {
if (!requestId) {
return;
}
this.printAllDecryptingMap();
HiLog.info(TAG, `hideOpeningDialogByFailed requestId ${requestId} showDialogState ${this._showDialogState}`);
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (!decryptingState) {
HiLog.info(TAG, 'hideOpeningDialogByFailed uri is not decrypting');
return;
}
decryptingState.needShowErr = true;
this.printAllDecryptingMap();
if (!this._showDialogState) {
return;
}
await this.hideDialog();
}
// 根据文件打包类型以及相应大小,判断是否拉起“正在打开”动效
// zip格式 > 10M;raw格式 > 200M
public async loadOpeningDialogByFileTypeAndSize(context: common.ServiceExtensionContext, fileSize: number,
openDlpFileData: OpenDlpFileData, state: DecryptState, parseType?: FileParseType): Promise<void> {
HiLog.info(TAG, `loadOpeningDialogByFileTypeAndSize fileSize: ${fileSize} parseType: ${parseType}`);
if ((parseType === FileParseType.ZIP && fileSize >= Constants.DIALOG_SHOW_SIZE_ZIP) ||
(parseType === FileParseType.RAW && fileSize >= Constants.DIALOG_SHOW_SIZE_RAW)) {
HiLog.info(TAG, 'need show opening dialog');
openDlpFileData.needShowToast = true;
await this.loadOpeningDialogUIExtAbility(context, openDlpFileData.requestId);
if (parseType === FileParseType.ZIP) {
// 只有zip格式会提前把状态设置为解密中,因为zip格式解析时间久
HiLog.info(TAG, 'zip type show opening dialog advance');
await this.showOpeningDialog(openDlpFileData.uri, openDlpFileData.requestId, true, true);
}
return;
}
HiLog.info(TAG, 'no need show opening dialog');
// 不需要拉“正在打开”动效,所以hasCallback值设置为true
this.setHasCallback(true);
}
// viewAbility正常结束——即解密请求处理完成后
public async unLoadOpeningDialogNormal(): Promise<void> {
this.printAllDecryptingMap();
this.setIsChargeDecrypting(false);
const isDecrypting = this._decryptingMap.size > 0;
HiLog.info(TAG, `OpeningDialogManager unLoadOpeningDialogNormal isDecrypting ${isDecrypting}`);
if (isDecrypting) {
HiLog.info(TAG, 'isDecrypting, no need hideDialog');
return;
}
await this.hideDialog();
}
// viewAbility异常结束——因为各种异常,比如低内存查杀或者没保活导致的进程死亡
public async unLoadOpeningDialogAbnormal(): Promise<void> {
HiLog.info(TAG, 'unLoadOpeningDialogAbnormal start');
this.setIsChargeDecrypting(false);
await this.hideDialog();
await ViewAbilityService.getInstance().sendDisconnectMsg();
}
private async clearTimeOut(): Promise<void> {
clearTimeout(this._callbackTimeOut);
this._callbackTimeOut = null;
}
private async callbackTimeOut(requestId: string): Promise<void> {
HiLog.info(TAG, 'to Open Dialog operation timed out after 3 seconds');
let hasCallback = this.getHasCallback();
if (!hasCallback) {
this.setHasCallback(true);
const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId);
if (decryptingState) {
await OpenDlpFileManager.getInstance().deleteStatus(decryptingState.uri);
}
this.deleteRequestId(requestId);
this.setIsChargeDecrypting(false);
this.printAllDecryptingMap();
this.clearTimeOut();
await TerminateView.terminate();
}
}
private setupTimeoutToOpenDialog(requestId: string): Promise<void> {
// 清除之前的定时器
if (this._callbackTimeOut) {
clearTimeout(this._callbackTimeOut);
}
// 调用requestModalUIExtension接口等待3秒
return new Promise<void>((resolve) => {
this._callbackTimeOut = setTimeout(() => {
this.callbackTimeOut(requestId);
resolve();
}, Constants.TO_OPEN_DIALOG_TIMEOUT_TIME);
});
}
// SEA拉起UEA
private async loadOpeningDialogUIExtAbility(context: common.ServiceExtensionContext,
requestId: string): Promise<boolean> {
HiLog.info(TAG, 'begin loadOpeningDialogUIExtAbility');
this.setHasCallback(false);
let uiExtWant: Want = {
bundleName: Constants.DLP_MANAGER_BUNDLE_NAME,
abilityName: Constants.DLP_OPENING_DIALOG_UI_EXT_ABILITY,
moduleName: 'entry',
parameters: {
'ability.want.params.uiExtensionType': 'sys/commonUI',
}
};
this.setupTimeoutToOpenDialog(requestId);
try {
await context.requestModalUIExtension(uiExtWant);
HiLog.info(TAG, 'requestModalUIExtension success');
return true;
} catch (err) {
HiLog.wrapError(TAG, err, 'requestModalUIExtension error');
this.setHasCallback(true);
return false;
}
}
}