1/* 2 * Copyright (c) 2023 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 display from '@ohos.display'; 17import emitter from '@ohos.events.emitter'; 18import extension from '@ohos.app.ability.ServiceExtensionAbility'; 19import window from '@ohos.window'; 20import CommonEventManager from '@ohos.commonEventManager'; 21import type Want from '@ohos.app.ability.Want'; 22import UIExtensionAbility from '@ohos.app.ability.UIExtensionAbility'; 23import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession'; 24import uiExtensionHost from '@ohos.uiExtensionHost'; 25import StartOptions from '@ohos.app.ability.StartOptions'; 26import configPolicy from '@ohos.configPolicy'; 27import fs from '@ohos.file.fs'; 28import Constants from '../common/constant'; 29 30 31 32const TAG = 'NotificationDialog_Service '; 33 34const UPDATE_INIT = 1; 35const UPDATE_NUM = 1; 36const UPDATE_BOUNDARY = 100; 37 38let eventSubscriber:CommonEventManager.CommonEventSubscriber; 39 40const enableNotificationDialogDestroyedEvent = { 41 eventId: 1, 42 priority: emitter.EventPriority.LOW 43}; 44 45const COMMON_EVENT_NAME = 'OnNotificationServiceDialogClicked'; 46enum DialogStatus { 47 ALLOW_CLICKED, 48 DENY_CLICKED, 49 DIALOG_CRASHED, 50 DIALOG_SERVICE_DESTROYED 51}; 52 53async function handleDialogQuitException(want: Want): Promise<void> { 54 CommonEventManager.publish( 55 COMMON_EVENT_NAME, 56 { 57 code: DialogStatus.DIALOG_CRASHED, 58 data: want.parameters.bundleName.toString(), 59 parameters: { 60 bundleName: want.parameters.bundleName.toString(), 61 bundleUid: want.parameters.bundleUid.toString() 62 } 63 } as CommonEventManager.CommonEventPublishData, 64 () => { console.info(TAG, 'publish DIALOG_CRASHED succeeded'); } 65 ); 66} 67 68interface NotificationConfig { 69 deviceInfo: DeviceInfo; 70} 71 72interface DeviceInfo { 73 isWatch: boolean; 74 isPc: boolean; 75} 76 77export class EnableNotificationDialog { 78 static ENABLE_NOTIFICATION_DIALOG_NAME = 'EnableNotificationDialog'; 79 static DIALOG_PATH = 'pages/notificationDialog'; 80 static WATCH_DIALOG_PATH = 'pages/watchNotificationDialog'; 81 static PC_DIALOG_PATH = 'pages/pcNotificationDialog'; 82 static TRANSPARANT_COLOR = '#00000000'; 83 static SCENEBOARD_BUNDLE = 'com.ohos.sceneboard'; 84 static SYSTEMUI_BUNDLE = 'com.ohos.systemui'; 85 86 id: number; 87 want: Want; 88 window: window.Window; 89 extensionWindow:uiExtensionHost.UIExtensionHostWindowProxy; 90 storage: LocalStorage; 91 stageModel: boolean; 92 subWindow: window.Window; 93 initSubWindowSize: boolean; 94 95 constructor(id: number, want: Want, stageModel: boolean) { 96 this.id = id; 97 this.want = want; 98 this.stageModel = stageModel; 99 this.window = undefined; 100 this.extensionWindow = undefined; 101 this.initSubWindowSize = false; 102 } 103 104 105 async createUiExtensionWindow(session: UIExtensionContentSession, stageModel: boolean): Promise<void> { 106 try { 107 let extensionWindow = session.getUIExtensionHostWindowProxy(); 108 this.extensionWindow = extensionWindow; 109 let shouldHide = true; 110 111 this.storage = new LocalStorage({ 112 'dialog': this, 113 'session': session 114 }); 115 116 let path = EnableNotificationDialog.DIALOG_PATH; 117 let hasConfig = true; 118 let isPcDevice = false; 119 try { 120 let filePaths = await configPolicy.getCfgFiles(Constants.CCM_CONFIG_PATH); 121 if (filePaths.length === 0) { 122 console.info(TAG, 'not get any configFile'); 123 hasConfig = false; 124 } 125 for (let i = 0; i < filePaths.length; i++) { 126 let res = fs.accessSync(filePaths[i]); 127 if (res) { 128 let fileContent = fs.readTextSync(filePaths[i]); 129 let config: NotificationConfig = JSON.parse(fileContent); 130 if (config.deviceInfo !== undefined) { 131 let deviceInfo: DeviceInfo = config.deviceInfo; 132 if (deviceInfo.isWatch !== undefined) { 133 path = EnableNotificationDialog.WATCH_DIALOG_PATH; 134 console.info(TAG, 'watch request'); 135 } 136 if (deviceInfo.isPc !== undefined) { 137 path = EnableNotificationDialog.PC_DIALOG_PATH; 138 isPcDevice = true; 139 console.info(TAG, 'pc request'); 140 } 141 } 142 } 143 } 144 } catch (err) { 145 console.error(TAG, 'Failed get ccm files'); 146 } 147 148 if (stageModel && hasConfig) { 149 let subWindowOpts : window.SubWindowOptions = { 150 'title': '', 151 decorEnabled: false, 152 isModal: true, 153 isTopmost: true 154 }; 155 let subWindow = await extensionWindow.createSubWindowWithOptions('subWindowForHost' + Date(), subWindowOpts); 156 this.subWindow = subWindow; 157 158 if(isPcDevice) { 159 let hasDisalogRectInfo = false; 160 let waiteTimes = 0; 161 extensionWindow.on('windowSizeChange', (data):void => { 162 console.info(TAG, `windowSizeChange ts event ${data?.width}, ${data?.height}`); 163 hasDisalogRectInfo = true; 164 }); 165 while(!hasDisalogRectInfo && waiteTimes < 10){ 166 waiteTimes ++; 167 await this.sleep(200); 168 } 169 if(hasDisalogRectInfo) { 170 let windowRect = extensionWindow.properties?.uiExtensionHostWindowProxyRect; 171 console.info(TAG, `size : ${windowRect?.left} ${windowRect?.top} ${windowRect?.width} ${windowRect?.height}`); 172 await subWindow.moveWindowToGlobal(windowRect?.left, windowRect?.top); 173 await subWindow.resize(windowRect?.width, windowRect?.height); 174 hasDisalogRectInfo = false; 175 } else { 176 console.info(TAG,'waite send windwow info fail'); 177 throw new Error('Failed to create window'); 178 } 179 } else { 180 let windowRect = extensionWindow.properties?.uiExtensionHostWindowProxyRect; 181 console.info(TAG, `size : ${windowRect?.left} ${windowRect?.top} ${windowRect?.width} ${windowRect?.height}`); 182 if (windowRect.width > 0 && windowRect.height > 0) { 183 console.log(TAG, `valid rect data`); 184 await subWindow.moveWindowToGlobal(windowRect?.left, windowRect?.top); 185 await subWindow.resize(windowRect?.width, windowRect?.height); 186 this.initSubWindowSize = true; 187 } 188 } 189 await subWindow.loadContent(path, this.storage); 190 try { 191 await subWindow.hideNonSystemFloatingWindows(true); 192 } catch (err) { 193 console.error(TAG, 'subWindow hideNonSystemFloatingWindows failed!'); 194 } 195 await subWindow.setWindowBackgroundColor(EnableNotificationDialog.TRANSPARANT_COLOR); 196 await subWindow.showWindow(); 197 } else { 198 await session.loadContent(path, this.storage); 199 try { 200 await extensionWindow.hideNonSecureWindows(shouldHide); 201 } catch (err) { 202 console.error(TAG, 'window hideNonSecureWindows failed!'); 203 } 204 await session.setWindowBackgroundColor(EnableNotificationDialog.TRANSPARANT_COLOR); 205 } 206 } catch (err) { 207 console.error(TAG, 'window create failed!'); 208 throw new Error('Failed to create window'); 209 } 210 } 211 212 async sleep(ms: number): Promise<void> { 213 return new Promise(resolve => setTimeout(resolve, ms)); 214 } 215 216 async publishButtonClickedEvent(enabled: boolean): Promise<void> { 217 CommonEventManager.publish( 218 COMMON_EVENT_NAME, 219 { 220 code: enabled ? DialogStatus.ALLOW_CLICKED : DialogStatus.DENY_CLICKED, 221 data: this.want.parameters.bundleName.toString(), 222 parameters: { 223 bundleName: this.want.parameters.bundleName.toString(), 224 bundleUid: this.want.parameters.bundleUid.toString() 225 } 226 } as CommonEventManager.CommonEventPublishData, 227 () => { console.info(TAG, 'publish CLICKED succeeded'); } 228 ); 229 } 230 231 async destroyException(): Promise<void> { 232 await handleDialogQuitException(this.want); 233 } 234 235 async destroy(): Promise<void> { 236 if (this.window !== undefined) { 237 emitter.emit(enableNotificationDialogDestroyedEvent, { 238 data: { 239 'id': this.id 240 } 241 }); 242 await this.destroyWindow(); 243 } 244 } 245 246 async destroyWindow(): Promise<void> { 247 await this.window.destroyWindow(); 248 this.window = undefined; 249 } 250}; 251 252 253class NotificationDialogServiceExtensionAbility extends UIExtensionAbility { 254 255 onCreate() { 256 console.log(TAG, `UIExtAbility onCreate`); 257 AppStorage.setOrCreate('context', this.context); 258 AppStorage.setOrCreate('isUpdate', UPDATE_INIT); 259 AppStorage.setOrCreate('clicked', false); 260 this.subscribe(); 261 262 } 263 264 async onSessionCreate(want: Want, session: UIExtensionContentSession) { 265 try { 266 let stageModel = false; 267 let bundleName = want.parameters['ohos.aafwk.param.callerBundleName']; 268 let bundleUid = want.parameters['ohos.aafwk.param.callerUid']; 269 if (bundleName !== EnableNotificationDialog.SCENEBOARD_BUNDLE && 270 bundleName !== EnableNotificationDialog.SYSTEMUI_BUNDLE) { 271 want.parameters.bundleName = bundleName; 272 want.parameters.bundleUid = bundleUid; 273 stageModel = true; 274 } else { 275 stageModel = false; 276 } 277 console.log(TAG, `UIExtAbility onSessionCreate bundleName ${want.parameters.bundleName}` + 278 `uid ${want.parameters.bundleUid}`); 279 let dialog = new EnableNotificationDialog(1, want, stageModel); 280 await dialog.createUiExtensionWindow(session, stageModel); 281 AppStorage.setOrCreate('dialog', dialog); 282 } catch (err) { 283 console.error(TAG, `Failed to handle onSessionCreate`); 284 await handleDialogQuitException(want); 285 this.context.terminateSelf(); 286 } 287 } 288 289 onForeground() { 290 console.log(TAG, `UIExtAbility onForeground`); 291 let dialog = AppStorage.get<EnableNotificationDialog>('dialog'); 292 293 if (dialog?.subWindow !== undefined) { 294 try { 295 dialog?.subWindow?.hideNonSystemFloatingWindows(true); 296 } catch (err) { 297 console.error(TAG, 'onForeground hideNonSystemFloatingWindows failed!'); 298 } 299 } else { 300 try { 301 dialog?.extensionWindow?.hideNonSecureWindows(true); 302 } catch (err) { 303 console.error(TAG, 'onForeground hideNonSecureWindows failed!'); 304 } 305 } 306 } 307 308 onBackground() { 309 console.log(TAG, `UIExtAbility onBackground`); 310 let dialog = AppStorage.get<EnableNotificationDialog>('dialog'); 311 312 if (dialog?.subWindow !== undefined) { 313 try { 314 dialog?.subWindow?.hideNonSystemFloatingWindows(false); 315 } catch (err) { 316 console.error(TAG, 'onBackground hideNonSystemFloatingWindows failed!'); 317 } 318 } else { 319 try { 320 dialog?.extensionWindow?.hideNonSecureWindows(false); 321 } catch (err) { 322 console.error(TAG, 'onBackground hideNonSecureWindows failed!'); 323 } 324 } 325 } 326 327 async onSessionDestroy(session: UIExtensionContentSession): Promise<void> { 328 console.log(TAG, `UIExtAbility onSessionDestroy`); 329 if (AppStorage.get('clicked') === false) { 330 console.log(TAG, `UIExtAbility onSessionDestroy unclick destory`); 331 let dialog = AppStorage.get<EnableNotificationDialog>('dialog'); 332 await dialog?.destroyException(); 333 } 334 } 335 336 async onDestroy(): Promise<void> { 337 console.info(TAG, 'UIExtAbility onDestroy.'); 338 await this.unsubscribe(); 339 await this.sleep(500); 340 this.context.terminateSelf(); 341 } 342 343 async sleep(ms: number): Promise<void> { 344 return new Promise(resolve => setTimeout(resolve, ms)); 345 } 346 347 async subscribe(): Promise<void> { 348 await CommonEventManager.createSubscriber( 349 { events: ['usual.event.BUNDLE_RESOURCES_CHANGED'] }) 350 .then((subscriber:CommonEventManager.CommonEventSubscriber) => { 351 eventSubscriber = subscriber; 352 }) 353 .catch((err) => { 354 console.log(TAG, `subscriber createSubscriber error code is ${err.code}, message is ${err.message}`); 355 }); 356 357 if (eventSubscriber === null) { 358 console.log(TAG, 'need create subscriber'); 359 return; 360 } 361 CommonEventManager.subscribe(eventSubscriber, (err, data) => { 362 if (err?.code) { 363 console.error(TAG, `subscribe callBack err= ${JSON.stringify(err)}`); 364 } else { 365 console.log(TAG, `subscribe callBack data= ${JSON.stringify(data)}`); 366 if (data.parameters?.bundleResourceChangeType !== 1) { 367 return; 368 } 369 console.log(TAG, `BUNDLE_RESOURCES_CHANGED-language change`); 370 let isUpdate:number = AppStorage.get('isUpdate'); 371 if (isUpdate === undefined || isUpdate > UPDATE_BOUNDARY) { 372 AppStorage.setOrCreate('isUpdate', UPDATE_NUM); 373 } else { 374 AppStorage.setOrCreate('isUpdate', ++isUpdate); 375 } 376 } 377 }); 378 } 379 380 async unsubscribe(): Promise<void> { 381 try { 382 if (eventSubscriber != null) { 383 CommonEventManager.unsubscribe(eventSubscriber, (err) => {}); 384 } 385 } catch (err) { 386 console.info('ubsubscribe fail'); 387 } 388 } 389} 390 391 392export default NotificationDialogServiceExtensionAbility; 393