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 bundleResourceManager from '@ohos.bundle.bundleResourceManager'; 17import display from '@ohos.display'; 18import window from '@ohos.window'; 19import { 20 titleTrim, 21 calContainerWidth, 22 getFontSizeScale, 23 sourceToVp, 24 getLimitFontSize } from '../common/utils'; 25import Constants from '../common/constant'; 26import fs from '@ohos.file.fs'; 27import configPolicy from '@ohos.configPolicy'; 28import { EnableNotificationDialog } from '../ServiceExtAbility/NotificationServiceExtAbility'; 29import { Callback} from '@ohos.base'; 30import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession'; 31import i18n from '@ohos.i18n'; 32import { MeasureOptions } from '@ohos.measure'; 33import { MeasureUtils } from '@ohos.arkui.UIContext'; 34import common from '@ohos.app.ability.common'; 35 36const TAG = 'NotificationDialog_Service '; 37 38const LAN_EN = 'These may include banners, sounds and more. You can change this in Settings.'; 39const LAN_CN = '通知提醒方式可能包括横幅、响铃等。可前往“设置” 更改。'; 40const LAN_HK = '通知提醒方式可能包括橫幅、響鬧等。可前往「設定」更改。'; 41const LAN_TW = '通知提醒方式可能包括橫幅、響鈴等。可前往「設定」變更。'; 42const LAN_BO = 'བརྡ་ཐོ་དྲན་སྐུལ་གྱི་ཐབས་ལམ་ལ་སྒོ་ལྕགས་བརྙན་ཡོལ་དང་། འཕྲེད་བྱང་། དྲིལ་སྒྲ། སྦིར་བརྡ་སོགས་ཚུད་སྲིད། ༼སྒྲིག་འགོད་༽ལ་བསྐྱོད་ནས་བཅོས་ཆོག'; 43const LAN_UG = 'قۇلۇپ ئېكرانى، بالداق، ئاۋاز ۋە تىترەش شۇلارنىڭ ئىچىدە. تەڭشەكتىن ئۆزگەرتەلەيسىز.'; 44 45const permission: Record<string, Resource> = { 46 'label': $r('app.string.group_label_notification'), 47 'icon': $r('app.media.ic_public_ring'), 48 'reason': $r('app.string.reason'), 49}; 50 51let storage = LocalStorage.getShared(); 52 53@Extend(Button) function customizeButton() { 54 .type(ButtonType.Normal) 55 .backgroundColor($r('sys.color.comp_background_tertiary')) 56 .fontColor($r('sys.color.font_emphasize')) 57 .fontSize( 58 getLimitFontSize( 59 sourceToVp($r('sys.float.Body_L')), 60 getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)) 61 ) 62 .fontWeight(FontWeight.Medium) 63 .height(Constants.PC_BUTTON_HEIGHT) 64 .width('50%') 65 .borderRadius(Constants.PC_RDIUS_8) 66 .flexGrow(Constants.FLEX_GROW) 67} 68 69@Entry(storage) 70@Component 71struct NotificationDialogPage { 72 @StorageLink('isUpdate') isUpdate: number = 0; 73 74 privacyDialogController: CustomDialogController = new CustomDialogController({ 75 builder: PermissionDialog({ isUpdate: $isUpdate }), 76 autoCancel: false, 77 alignment: DialogAlignment.Center, 78 customStyle: true, 79 cornerRadius: Constants.PC_RDIUS_16, 80 maskColor: $r('sys.color.ohos_id_color_mask_thin'), 81 onWillDismiss: (dismissDialogAction: DismissDialogAction) => { 82 console.info(TAG, `dialog onWillDismiss reason= : ${JSON.stringify(dismissDialogAction.reason)}`); 83 } 84 }); 85 86 build() {} 87 88 aboutToAppear() { 89 this.privacyDialogController.open(); 90 } 91 92 onPageShow() { 93 } 94} 95 96@CustomDialog 97struct PermissionDialog { 98 @State appName: string = ''; 99 @State content: string = LAN_CN; 100 @State naviHeight: number = 0; 101 @State isBottomPopover: boolean = false; 102 @StorageLink('clicked') clicked: boolean = false; 103 @Link @Watch('updateOnPageShow') isUpdate: number; 104 dialog?: EnableNotificationDialog; 105 session?: UIExtensionContentSession; 106 controller?: CustomDialogController; 107 @State titleContainerWidth: string | number = 'auto'; 108 109 build() { 110 Row() { 111 Flex({ justifyContent: FlexAlign.Center, alignItems: this.isBottomPopover ? ItemAlign.End : ItemAlign.Center }) { 112 Column() { 113 Scroll() { 114 Column() { 115 Row() { 116 Image(permission.icon) 117 .width(Constants.DIALOG_ICON_WIDTH) 118 .height(Constants.DIALOG_ICON_HEIGHT) 119 .margin({ 120 top: Constants.PC_ICON_MARGIN_TOP 121 }) 122 .draggable(false) 123 } 124 Row() { 125 Flex({ justifyContent: FlexAlign.Center }) { 126 Text($r('app.string.group_label_notification', this.appName)) 127 .fontSize($r('sys.float.Title_S')) 128 .fontColor($r('sys.color.font_primary')) 129 .fontWeight(FontWeight.Bold) 130 .minFontSize( 131 getLimitFontSize(Constants.TITLE_MIN_FONT_SIZE, 132 getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)) 133 ) 134 .maxFontSize( 135 getLimitFontSize(sourceToVp($r('sys.float.Title_S')), 136 getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)) 137 ) 138 .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST) 139 .maxLines(2) 140 .textOverflow({overflow: TextOverflow.Ellipsis}) 141 .width(this.titleContainerWidth) 142 .textAlign(TextAlign.Center) 143 } 144 .margin({ 145 left: Constants.PC_TITLE_MARGIN_SIDE, 146 right: Constants.PC_TITLE_MARGIN_SIDE, 147 top: Constants.PC_TITLE_MARGIN_TOP, 148 }) 149 .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => { 150 let containerWidth = newValue.width as number; 151 let options: MeasureOptions = { 152 textContent: $r('app.string.group_label_notification', this.appName), 153 fontSize: getLimitFontSize(sourceToVp($r('sys.float.Title_S')), 154 getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)), 155 fontWeight: FontWeight.Bold, 156 }; 157 this.titleContainerWidth = calContainerWidth(containerWidth, options, 158 Constants.CROSS_LINE_RATIO, this.getUIContext().getMeasureUtils()); 159 console.info(TAG, `onSizeChange titleContainerWidth: ${this.titleContainerWidth}`); 160 }) 161 } 162 Row() { 163 Flex({ justifyContent: FlexAlign.Center }) { 164 Text() { 165 Span(this.content) 166 } 167 .fontSize( 168 getLimitFontSize(sourceToVp($r('sys.float.Body_L')), 169 getFontSizeScale(getContext(this) as common.UIAbilityContext)) 170 ) 171 .fontWeight(FontWeight.Medium) 172 .fontColor($r('sys.color.font_primary')) 173 .margin({ 174 left: Constants.PC_CONTENT_MARGIN_SIDE, 175 right: Constants.PC_CONTENT_MARGIN_SIDE, 176 top: Constants.PC_CONTENT_MARGIN_TOP 177 }) 178 } 179 } 180 Row() { 181 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 182 Button($r('app.string.BAN')) 183 .onClick(async (): Promise<void> => { 184 await this.enableNotification(false); 185 }) 186 .customizeButton() 187 Divider() 188 .color($r('sys.color.comp_divider')) 189 .vertical(true) 190 .height(Constants.DIVIDER_HEIGHT) 191 .strokeWidth(Constants.DIVIDER_WIDTH) 192 .margin({left: Constants.BUTTON_LEFT, right: Constants.BUTTON_RIGHT}) 193 Button($r('app.string.ALLOW')) 194 .onClick(async (): Promise<void> => { 195 await this.enableNotification(true); 196 }) 197 .customizeButton() 198 } 199 .margin({ 200 left: Constants.PC_OPERATE_MARGIN_SIDE, 201 right: Constants.PC_OPERATE_MARGIN_SIDE, 202 top: Constants.PC_OPERATE_MARGIN_TOP, 203 bottom: Constants.PC_OPERATE_MARGIN_BUTTOM 204 }) 205 } 206 } 207 } 208 .constraintSize({ maxHeight: Constants.MAXIMUM_HEADER_HEIGHT }) 209 } 210 .borderRadius(Constants.PC_RDIUS_16) 211 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) 212 .width(Constants.PC_DIALOG_WIDTH) 213 .outline({ 214 width: 1, 215 color: Constants.PC_OUTLINE_COLOR, 216 radius: Constants.PC_RDIUS_16 217 }) 218 .clip(true) 219 } 220 .width(Constants.FULL_WIDTH) 221 .height(Constants.FULL_HEIGHT) 222 } 223 } 224 225 async aboutToAppear(): Promise<void> { 226 this.dialog = storage.get('dialog') as EnableNotificationDialog; 227 this.session = storage.get('session') as UIExtensionContentSession; 228 this.updateAvoidWindow(); 229 this.updateSubWindowSize(); 230 try { 231 await this.updateStatus(); 232 } catch (err) { 233 console.error(TAG, `aboutToAppear error : ${err?.code}`); 234 await this.dialog?.destroyException(); 235 await this.session?.terminateSelf(); 236 } 237 } 238 239 async aboutToDisappear(): Promise<void> { 240 console.info(TAG, `aboutToDisappear`); 241 this.session?.terminateSelf(); 242 } 243 244 async updateOnPageShow(): Promise<void> { 245 if (this.isUpdate > 0) { 246 await this.updateStatus(); 247 } 248 } 249 250 async updateStatus(): Promise<void> { 251 let bundleNameObj = this.dialog?.want.parameters?.bundleName; 252 let bundleName = bundleNameObj ? bundleNameObj.toString() : ''; 253 await this.updateApplicationName(bundleName); 254 } 255 256 async updateApplicationName(bundleName: string): Promise<void> { 257 console.info(TAG, `updateApplicationName bundleName: ${bundleName}`); 258 try { 259 let bundleFlags = bundleResourceManager.ResourceFlag.GET_RESOURCE_INFO_ALL; 260 let resourceInfo = bundleResourceManager.getBundleResourceInfo(bundleName, bundleFlags); 261 console.info(TAG, `applicationName name : ${JSON.stringify(resourceInfo.label)}`); 262 let appName = resourceInfo.label; 263 this.appName = titleTrim(appName); 264 console.info(TAG, `hap label: ${this.appName}`); 265 266 let systemLanguage: string = i18n.System.getSystemLanguage(); 267 console.info(TAG, `language: ${systemLanguage}`); 268 if (systemLanguage.indexOf('zh-Hans') != -1) { 269 this.content = LAN_CN; 270 } else if (systemLanguage.indexOf('zh-Hant') != -1) { 271 this.content = LAN_TW; 272 } else if (systemLanguage.indexOf('en') != -1) { 273 this.content = LAN_EN; 274 } else if (systemLanguage.indexOf('ug') != -1) { 275 this.content = LAN_UG; 276 } else if (systemLanguage.indexOf('bo') != -1) { 277 this.content = LAN_BO; 278 } 279 280 } catch (err) { 281 console.error(TAG, `applicationName error : ${err?.code}`); 282 } 283 } 284 285 updateAvoidWindow(): void { 286 let type = window.AvoidAreaType.TYPE_SYSTEM; 287 try { 288 this.dialog?.extensionWindow.on('avoidAreaChange', (data): void => { 289 if (data.type == window.AvoidAreaType.TYPE_SYSTEM) { 290 console.info(TAG, `avoidAreaChange: ${JSON.stringify(data)}`); 291 this.naviHeight = data.area.bottomRect.height; 292 } 293 }); 294 let avoidArea = this.dialog?.extensionWindow.getWindowAvoidArea(type); 295 if (avoidArea != undefined) { 296 console.info(TAG, `avoidArea: ${JSON.stringify(avoidArea)}`); 297 this.naviHeight = avoidArea.bottomRect.height; 298 } 299 } catch (err) { 300 console.error(TAG, `Failed to obtain the area. Cause: ${err?.code}`); 301 } 302 } 303 304 updateSubWindowSize(): void { 305 try { 306 this.dialog?.extensionWindow.on('windowSizeChange', (data):void => { 307 let windowRect = this.dialog?.extensionWindow.properties?.uiExtensionHostWindowProxyRect; 308 console.info(TAG, `windowSizeChange event, size = ${windowRect?.left}-${windowRect?.top}-${windowRect?.width}-${windowRect?.height}`); 309 this.dialog?.subWindow?.moveWindowTo(windowRect?.left, windowRect?.top); 310 this.dialog?.subWindow?.resize(windowRect?.width, windowRect?.height); 311 }); 312 } catch (err) { 313 console.error(TAG, `updateSubWindowSize error. Cause: ${err?.code}`); 314 } 315 } 316 317 async enableNotification(enabled: boolean): Promise<void> { 318 console.info(TAG, `NotificationDialog enableNotification: ${enabled}`); 319 try { 320 await this.dialog?.publishButtonClickedEvent(enabled); 321 this.clicked = true; 322 } catch (err) { 323 console.error(TAG, `NotificationDialog enable error, code is ${err?.code}`); 324 await this.dialog?.destroyException(); 325 } finally { 326 await this.dialog?.subWindow?.destroyWindow(); 327 this.session?.terminateSelf(); 328 } 329 } 330}