1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import Constants from '../../common/constant'; 17import { HiLog } from '../../common/HiLog'; 18import { common, Want } from '@kit.AbilityKit'; 19import ViewAbilityService from '../../rpc/ViewAbility/service/ViewAbilityService'; 20import { DecryptState, OpenDlpFileManager } from './OpenDlpFileManager'; 21import { FileParseType } from '../../bean/data/FileParseType'; 22import OpenDlpFileData from '../data/OpenDlpFileData'; 23import FileUtils from '../../common/FileUtils/FileUtils'; 24import ErrorManager from '../handler/ErrorHandler'; 25import { emitter } from '@kit.BasicServicesKit'; 26import StartSandboxHandler from '../handler/StartSandboxHandler'; 27 28const TAG = 'OpeningDialogManager'; 29 30interface DecryptingState { 31 uri: string, 32 dialogTimeout: boolean, // 弹框是否超过250ms,拉弹框之前状态设置未false,超时回调设置为true 33 needStartAbility: boolean, // 弹框超时后,是否需要拉沙箱 34 hasDecrypted: boolean, // 用于判断是否是用户主动取消“正在打开弹框”场景 35 needShowDialog: boolean, // 用于判断是否需要“正在打开”弹框 36 isZip: boolean, // 用于zip格式,用户取消“正在打开”弹框判断 37} 38 39export default class OpeningDialogManager { 40 private static instance: OpeningDialogManager; 41 // 是否正在弹框 42 private _showDialogState: boolean = false; 43 // SEA拉起UEA后,是否注册回调 44 private _hasCallback: boolean = false; 45 // 是否正在处理弹框逻辑 46 private _isChargeDecrypting: boolean = false; 47 private _decryptingMap: Map<string, DecryptingState>; 48 private _isTerminalSelf: boolean = false; 49 private _isWaitingShowToast: boolean = false; 50 private checkShowDialogStateEvent = async () => { 51 HiLog.info(TAG, 'receive CHECK_SHOW_DIALOG_STATE'); 52 this.checkShowDialogState(); 53 }; 54 55 private constructor() { 56 this._decryptingMap = new Map<string, DecryptingState>(); 57 emitter.on(Constants.CHECK_SHOW_DIALOG_STATE, this.checkShowDialogStateEvent); 58 } 59 60 public unRegisterEmitEvent() { 61 HiLog.debug(TAG, 'unRegisterEmitEvent'); 62 emitter.off(Constants.CHECK_SHOW_DIALOG_STATE, this.checkShowDialogStateEvent); 63 } 64 65 private printAllDecryptingMap(): void { 66 HiLog.debug(TAG, `printAllDecryptingMap ${this._decryptingMap.size}`); 67 this._decryptingMap.forEach((value, key) => { 68 HiLog.debug(TAG, `key ${FileUtils.getFileNameByUri(key)}, uri ${FileUtils.getFileNameByUri(value.uri)}, 69 dialogTimeout ${value.dialogTimeout}, needStartAbility ${value.needStartAbility}, 70 hasDecrypted ${value.hasDecrypted}, needShowDialog ${value.needShowDialog}, isZip ${value.isZip}`); 71 }); 72 } 73 74 static getInstance(): OpeningDialogManager { 75 if (!OpeningDialogManager.instance) { 76 OpeningDialogManager.instance = new OpeningDialogManager(); 77 } 78 return OpeningDialogManager.instance; 79 } 80 81 public setIsWaitingShowToast(value: boolean) { 82 this._isWaitingShowToast = value; 83 } 84 85 public getIsWaitingShowToast(): boolean { 86 return this._isWaitingShowToast; 87 } 88 89 public getShowDialogState(): boolean { 90 HiLog.debug(TAG, `getShowDialogState ${this._showDialogState}`); 91 return this._showDialogState; 92 } 93 94 public getHasCallback(): boolean { 95 HiLog.debug(TAG, `getHasCallback ${this._hasCallback}`); 96 return this._hasCallback; 97 } 98 99 public setIsTerminalSelf(value: boolean) { 100 HiLog.debug(TAG, `setIsTerminalSelf ${value}`); 101 this._isTerminalSelf = value; 102 } 103 104 public getIsTerminalSelf(): boolean { 105 HiLog.debug(TAG, `getIsTerminalSelf ${this._isTerminalSelf}`); 106 return this._isTerminalSelf; 107 } 108 109 public getIsDecryptingByRequestId(requestId: string): boolean { 110 this.printAllDecryptingMap(); 111 const isDecrypting = this._decryptingMap.has(requestId); 112 HiLog.info(TAG, `getIsDecryptingByRequestId requestId ${requestId} isDecrypting ${isDecrypting}`); 113 return isDecrypting; 114 } 115 116 public getIsDecrypting(): boolean { 117 this.printAllDecryptingMap(); 118 const isDecrypting = this._decryptingMap.size > 0; 119 HiLog.info(TAG, `getIsDecrypting isDecrypting ${isDecrypting}`); 120 return isDecrypting; 121 } 122 123 public setIsChargeDecrypting(value: boolean) { 124 HiLog.info(TAG, `setIsChargeDecrypting ${value}`); 125 this._isChargeDecrypting = value; 126 } 127 128 public getIsChargeDecrypting(): boolean { 129 HiLog.info(TAG, `getIsChargeDecrypting ${this._isChargeDecrypting}`); 130 return this._isChargeDecrypting; 131 } 132 133 public deleteRequestId(requestId: string) { 134 this.printAllDecryptingMap(); 135 HiLog.info(TAG, `deleteRequestId requestId ${requestId}`); 136 this._decryptingMap.delete(requestId); 137 } 138 139 // 判断是否需要show toast,这次不需要就等timeout的回调 140 public getCanShowToast(): boolean { 141 this.printAllDecryptingMap(); 142 const requestId = this.getDecryptingRequestIdByToast(); 143 if (!requestId) { 144 HiLog.error(TAG, 'no getDecryptingRequestIdByToast'); 145 return true; 146 } 147 const decryptingState = this._decryptingMap.get(requestId); 148 const dialogTimeout = decryptingState?.dialogTimeout ?? true; 149 const needShowDialog = decryptingState?.needShowDialog ?? true; 150 HiLog.info(TAG, `getCanShowToast dialogTimeout ${dialogTimeout} needShowDialog ${needShowDialog} 151 showDialogState ${this._showDialogState}`); 152 if (!needShowDialog || dialogTimeout) { 153 return true; 154 } 155 if (!dialogTimeout && this._showDialogState) { 156 return false; 157 } 158 return true; 159 } 160 161 // 判断是否需要拉起沙箱,这次不需要就等timeout的回调 162 public getNeedStartAbility(requestId: string): boolean { 163 this.printAllDecryptingMap(); 164 HiLog.info(TAG, `OpeningDialogManager getNeedStartAbility requestId ${requestId}`); 165 const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId); 166 if (!decryptingState) { 167 HiLog.error(TAG, 'getNeedStartAbility not isDecrypting'); 168 return false; 169 } 170 const timeout = decryptingState.dialogTimeout; 171 HiLog.info(TAG, `OpeningDialogManager getNeedStartAbility requestId ${requestId}, timeout ${timeout}, 172 showDialogState ${this._showDialogState}`); 173 if (!this._showDialogState || timeout) { 174 HiLog.info(TAG, 'can startAbility'); 175 return true; 176 } 177 decryptingState.needStartAbility = true; 178 this.printAllDecryptingMap(); 179 return false; 180 } 181 182 // 判断是否可以拉起沙箱,即用户是否手动终止弹框 183 public getCanStartAbility(requestId: string): boolean { 184 this.printAllDecryptingMap(); 185 const isDecrypting = this._decryptingMap.has(requestId); 186 HiLog.info(TAG, `getCanStartAbility requestId ${requestId}, isDecrypting ${isDecrypting}`); 187 return isDecrypting; 188 } 189 190 // 弹框消失的回调,需要判断是否是用户主动终止解密流程 191 public async dialogDisappear(requestId: string): Promise<void> { 192 this.printAllDecryptingMap(); 193 HiLog.info(TAG, `OpeningDialogManager dialogDisappear requestId ${requestId}`); 194 this._showDialogState = false; 195 const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId); 196 if (!decryptingState) { 197 HiLog.info(TAG, 'dialogDisappear uri is not decrypting'); 198 return; 199 } 200 const hasDecrypted = decryptingState.hasDecrypted; 201 const isZip = decryptingState.isZip; 202 HiLog.info(TAG, `dialogDisappear hasDecrypted ${hasDecrypted} isZip ${isZip}`); 203 if (!hasDecrypted || (!hasDecrypted && isZip)) { // 如果正在解密且不需要拉弹框,是用户主动终止解密流程 204 HiLog.info(TAG, 'user close opening dialog'); 205 await OpenDlpFileManager.getInstance().deleteStatus(decryptingState.uri); 206 this._decryptingMap.delete(requestId); 207 this._isChargeDecrypting = false; 208 this.printAllDecryptingMap(); 209 } 210 } 211 212 // 弹框超过250ms的回调 213 public async dialogTimeout(requestId: string): Promise<void> { 214 this.printAllDecryptingMap(); 215 HiLog.info(TAG, `OpeningDialogManager dialogTimeout requestId ${requestId}`); 216 const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId); 217 if (!decryptingState) { 218 HiLog.info(TAG, 'dialogTimeout uri is not decrypting'); 219 await ErrorManager.getInstance().startHandleError(requestId); 220 return; 221 } 222 decryptingState.dialogTimeout = true; 223 await ErrorManager.getInstance().startHandleError(requestId); 224 this.printAllDecryptingMap(); 225 if (decryptingState.needStartAbility) { // 弹框超时,且需要拉起沙箱,就去拉沙箱 226 const startSandboxRet = await StartSandboxHandler.getInstance().startSandbox(); 227 if (startSandboxRet.errcode !== Constants.ERR_CODE_SUCCESS) { 228 HiLog.error(TAG, 'OpeningDialogManager startSandbox error'); 229 } 230 this._decryptingMap.delete(requestId); 231 } 232 } 233 234 private async showDialog(requestId: string): Promise<void> { 235 this.printAllDecryptingMap(); 236 this._showDialogState = await ViewAbilityService.getInstance().showDialog(true, requestId); 237 HiLog.info(TAG, `OpeningDialogManager showDialog requestId: ${requestId}, state: ${this._showDialogState}`); 238 } 239 240 private async hideDialog(): Promise<void> { 241 HiLog.info(TAG, 'OpeningDialogManager hideDialog start'); 242 await ViewAbilityService.getInstance().showDialog(false); 243 } 244 245 private getDecryptingRequestId(): string | undefined { 246 this.printAllDecryptingMap(); 247 for (const entry of this._decryptingMap) { 248 if (!entry[1].needStartAbility && !entry[1].hasDecrypted) { 249 HiLog.info(TAG, `getDecryptingRequestId found requestId ${entry[0]}`); 250 return entry[0]; 251 } 252 } 253 HiLog.info(TAG, 'getDecryptingRequestId not found'); 254 return undefined; 255 } 256 257 private getDecryptingRequestIdByToast(): string | undefined { 258 this.printAllDecryptingMap(); 259 for (const entry of this._decryptingMap) { 260 if (entry[1].hasDecrypted) { 261 HiLog.info(TAG, `getDecryptingRequestIdByToast found requestId ${entry[0]}`); 262 return entry[0]; 263 } 264 } 265 HiLog.info(TAG, 'getDecryptingRequestIdByToast not found'); 266 return undefined; 267 } 268 269 270 // 根据解密状态,校验是否需要拉起弹框 271 public async checkShowDialogState(): Promise<void> { 272 this.printAllDecryptingMap(); 273 this._hasCallback = true; 274 const isDecrypting = this._decryptingMap.size > 0; 275 const requestId = this.getDecryptingRequestId(); 276 HiLog.info(TAG, `OpeningDialogManager checkShowDialogState showDialogState ${this._showDialogState}, 277 isDecrypting ${isDecrypting}, requestId ${requestId}, isChargeDecrypting ${this._isChargeDecrypting}`); 278 279 if (!isDecrypting && !this._isChargeDecrypting) { // 没在处理弹框逻辑或解密逻辑,需要取消弹框 280 await this.hideDialog(); 281 await ViewAbilityService.getInstance().sendDisconnectMsgWithTimeout(); 282 return; 283 } 284 285 if (!requestId) { 286 HiLog.info(TAG, 'OpeningDialogManager checkShowDialogState getDecryptingRequestId not found'); 287 return; 288 } 289 if (isDecrypting && !this._showDialogState) { // 正在解密,且没拉起弹框,需要拉起弹框 290 this._showDialogState = true; 291 await this.showDialog(requestId); 292 return; 293 } 294 } 295 296 // openDLPFile前调用,或者zip格式提前调用 297 public async showOpeningDialog(uri: string, requestId: string, needShowDialog: boolean, 298 isZip?: boolean): Promise<void> { 299 this.printAllDecryptingMap(); 300 HiLog.info(TAG, 'OpeningDialogManager showOpeningDialog'); 301 if (this._decryptingMap.has(requestId)) { 302 HiLog.info(TAG, 'OpeningDialogManager showOpeningDialog is decrypting'); 303 return; 304 } 305 const decryptingState: DecryptingState = { 306 uri: uri, 307 dialogTimeout: false, 308 needStartAbility: false, 309 hasDecrypted: false, 310 needShowDialog: needShowDialog, 311 isZip: isZip ?? false, 312 } 313 this._decryptingMap.set(requestId, decryptingState); 314 this.printAllDecryptingMap(); 315 if (this._showDialogState) { 316 HiLog.info(TAG, 'is showing OpeningDialog'); 317 return; 318 } 319 if (!needShowDialog) { 320 HiLog.info(TAG, 'no need showDialog'); 321 return; 322 } 323 await this.showDialog(requestId); 324 } 325 326 // 根据大小不需要弹框,但是调用penDLPFile接口超过500ms都没返回,拉起“正在打开”弹框 327 public async showOpeningDialogByTimeout(requestId: string): Promise<void> { 328 const viewContext = AppStorage.get('viewContext') as common.ServiceExtensionContext; 329 if (!viewContext) { 330 HiLog.error(TAG, 'showOpeningDialogByTimeout viewContext null'); 331 return; 332 } 333 this.printAllDecryptingMap(); 334 HiLog.info(TAG, 'OpeningDialogManager showOpeningDialogByTimeout'); 335 const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId); 336 if (!decryptingState) { 337 HiLog.error(TAG, 'showOpeningDialogByTimeout not isDecrypting'); 338 return; 339 } 340 decryptingState.needShowDialog = true; 341 this.printAllDecryptingMap(); 342 if (this._showDialogState) { 343 HiLog.info(TAG, 'showOpeningDialogByTimeout is showing OpeningDialog'); 344 return; 345 } 346 await this.loadOpeningDialogUIExtAbility(viewContext); 347 await this.showDialog(requestId); 348 } 349 350 // openDLPFile结束后调用 351 public async hideOpeningDialog(requestId: string): Promise<void> { 352 this.printAllDecryptingMap(); 353 HiLog.info(TAG, `OpeningDialogManager hideOpeningDialog requestId ${requestId}`); 354 const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId); 355 if (!decryptingState) { 356 HiLog.info(TAG, 'hideOpeningDialog uri is not decrypting'); 357 return; 358 } 359 decryptingState.hasDecrypted = true; 360 this.printAllDecryptingMap(); 361 HiLog.info(TAG, `hideOpeningDialog showDialogState ${this._showDialogState}`); 362 if (!this._showDialogState) { 363 return; 364 } 365 await this.hideDialog(); 366 } 367 368 // 拉沙箱之前调用 369 public setNeedStartAbility(requestId: string): void { 370 this.printAllDecryptingMap(); 371 HiLog.info(TAG, `OpeningDialogManager setNeedStartAbility requestId ${requestId}`); 372 const decryptingState: DecryptingState | undefined = this._decryptingMap.get(requestId); 373 if (!decryptingState) { 374 HiLog.info(TAG, 'setNeedStartAbility uri is not decrypting'); 375 return; 376 } 377 decryptingState.needStartAbility = true; 378 this._isChargeDecrypting = false; 379 this.printAllDecryptingMap(); 380 } 381 382 // zip格式提前调用后因为各种原因失败 383 public async hideOpeningDialogByFailed(requestId: string): Promise<void> { 384 this.printAllDecryptingMap(); 385 const isDecrypting = this._decryptingMap.has(requestId); 386 HiLog.info(TAG, `hideOpeningDialogByFailed requestId ${requestId}, isDecrypting ${isDecrypting}`); 387 if (!isDecrypting) { 388 HiLog.info(TAG, 'hideOpeningDialogByFailed uri is not decrypting'); 389 return; 390 } 391 HiLog.info(TAG, `hideOpeningDialogByFailed showDialogState ${this._showDialogState}`); 392 if (!this._showDialogState) { 393 return; 394 } 395 await this.hideDialog(); 396 await ViewAbilityService.getInstance().sendDisconnectMsgWithTimeout(); 397 } 398 399 // 根据文件打包类型,文件大小判断是否拉起弹框 400 public async loadOpeningDialog(context: common.ServiceExtensionContext, fileSize: number, 401 openDlpFileData: OpenDlpFileData, state: DecryptState, parseType?: FileParseType): Promise<void> { 402 HiLog.info(TAG, `OpeningDialogManager loadOpeningDialog fileSize: ${fileSize} parseType: ${parseType}`); 403 if ((parseType === FileParseType.ZIP && fileSize >= Constants.DIALOG_SHOW_SIZE_ZIP) || 404 (parseType === FileParseType.RAW && fileSize >= Constants.DIALOG_SHOW_SIZE_RAW)) { 405 HiLog.info(TAG, 'need show opening dialog'); 406 openDlpFileData.needShowToast = true; 407 this._hasCallback = false; 408 await this.loadOpeningDialogUIExtAbility(context); 409 if (parseType === FileParseType.ZIP) { 410 HiLog.info(TAG, 'zip type show opening dialog advance'); 411 await this.showOpeningDialog(openDlpFileData.uri, openDlpFileData.requestId, true, true); 412 } 413 return; 414 } 415 HiLog.info(TAG, 'no need show opening dialog'); 416 this._hasCallback = true; 417 } 418 419 // viewAbility正常结束 420 public async unLoadOpeningDialogNormal(): Promise<void> { 421 this.printAllDecryptingMap(); 422 this._isChargeDecrypting = false; 423 const isDecrypting = this._decryptingMap.size > 0; 424 HiLog.info(TAG, `OpeningDialogManager unLoadOpeningDialogNormal isDecrypting ${isDecrypting}`); 425 if (isDecrypting) { 426 HiLog.info(TAG, 'isDecrypting, no need hideDialog'); 427 return; 428 } 429 await this.hideDialog(); 430 } 431 432 // viewAbility异常结束 433 public async unLoadOpeningDialogAbnormal(): Promise<void> { 434 HiLog.info(TAG, 'unLoadOpeningDialogAbnormal start'); 435 this._isChargeDecrypting = false; 436 await this.hideDialog(); 437 await ViewAbilityService.getInstance().sendDisconnectMsg(); 438 } 439 440 private async loadOpeningDialogUIExtAbility(context: common.ServiceExtensionContext): Promise<boolean> { 441 HiLog.info(TAG, 'begin loadOpeningDialog'); 442 let uiExtWant: Want = { 443 bundleName: Constants.DLP_MANAGER_BUNDLE_NAME, 444 abilityName: Constants.DLP_OPENING_DIALOG_UI_EXT_ABILITY, 445 moduleName: 'entry', 446 parameters: { 447 'ability.want.params.uiExtensionType': 'sys/commonUI', 448 } 449 }; 450 try { 451 await context.requestModalUIExtension(uiExtWant); 452 HiLog.info(TAG, 'requestModalUIExtension success'); 453 return true; 454 } catch (err) { 455 HiLog.wrapError(TAG, err, 'requestModalUIExtension error'); 456 return false; 457 } 458 } 459}