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 { MeasureOptions } from '@ohos.measure'; 32import { MeasureUtils } from '@ohos.arkui.UIContext'; 33import common from '@ohos.app.ability.common'; 34 35const TAG = 'NotificationDialog_Service '; 36const permission: Record<string, Resource> = { 37 'label': $r('app.string.group_label_notification'), 38 'icon': $r('app.media.ic_public_ring'), 39 'reason': $r('app.string.reason'), 40}; 41const bottomPopoverTypes = ['default', 'phone']; 42 43let storage = LocalStorage.getShared(); 44 45@Extend(Button) function customizeButton() { 46 .backgroundColor(Color.Transparent) 47 .fontColor($r('app.color.button_text')) 48 .fontSize( 49 getLimitFontSize( 50 Constants.TEXT_MIDDLE_FONT_SIZE, 51 getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)) 52 ) 53 .fontWeight(FontWeight.Medium) 54 .height(Constants.BUTTON_HEIGHT) 55 .flexGrow(Constants.FLEX_GROW) 56 .width('50%') 57} 58 59@Entry(storage) 60@Component 61struct NotificationDialogPage { 62 @StorageLink('isUpdate') isUpdate: number = 0; 63 64 privacyDialogController: CustomDialogController = new CustomDialogController({ 65 builder: PermissionDialog({ isUpdate: $isUpdate }), 66 autoCancel: false, 67 alignment: DialogAlignment.Center, 68 customStyle: true, 69 maskColor: $r('app.color.mask_thin'), 70 onWillDismiss: (dismissDialogAction: DismissDialogAction) => { 71 console.info(TAG, `dialog onWillDismiss reason= : ${JSON.stringify(dismissDialogAction.reason)}`); 72 } 73 }); 74 75 build() {} 76 77 aboutToAppear() { 78 this.privacyDialogController.open(); 79 } 80 81 onPageShow() { 82 } 83} 84 85@CustomDialog 86struct PermissionDialog { 87 @State appName: string = ''; 88 @State naviHeight: number = 0; 89 @State isBottomPopover: boolean = true; 90 @StorageLink('clicked') clicked: boolean = false; 91 @Link @Watch('updateOnPageShow') isUpdate: number; 92 dialog?: EnableNotificationDialog; 93 session?: UIExtensionContentSession; 94 controller?: CustomDialogController; 95 @State titleContainerWidth: string | number = 'auto'; 96 97 build() { 98 Row(){ 99 Flex({ justifyContent: FlexAlign.Center, alignItems: this.isBottomPopover ? ItemAlign.End : ItemAlign.Center }) { 100 Column() { 101 Scroll() { 102 Column() { 103 Row() { 104 Image(permission.icon) 105 .width(Constants.DIALOG_ICON_WIDTH) 106 .height(Constants.DIALOG_ICON_HEIGHT) 107 .margin({ 108 top: Constants.DIALOG_ICON_MARGIN_TOP 109 }) 110 } 111 Row() { 112 Flex({ justifyContent: FlexAlign.Center }) { 113 Text($r('app.string.group_label_notification', this.appName)) 114 .fontSize($r('sys.float.ohos_id_text_size_headline8')) 115 .fontColor($r('app.color.text_primary')) 116 .fontWeight(FontWeight.Bold) 117 .minFontSize( 118 getLimitFontSize(Constants.TITLE_MIN_FONT_SIZE, 119 getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)) 120 ) 121 .maxFontSize( 122 getLimitFontSize(sourceToVp($r('sys.float.ohos_id_text_size_headline8')), 123 getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)) 124 ) 125 .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST) 126 .maxLines(2) 127 .textOverflow({overflow: TextOverflow.Ellipsis}) 128 .width(this.titleContainerWidth) 129 .textAlign(TextAlign.Center) 130 } 131 .margin({ 132 top: Constants.DIALOG_REQ_MARGIN_TOP, 133 bottom:Constants.DIALOG_REQ_MARGIN_BUTTOM, 134 left: Constants.DIALOG_REQ_MARGIN_LEFT, 135 right: Constants.DIALOG_REQ_MARGIN_RIGHT 136 }) 137 .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => { 138 let containerWidth = newValue.width as number; 139 let options: MeasureOptions = { 140 textContent: $r('app.string.group_label_notification', this.appName), 141 fontSize: getLimitFontSize(sourceToVp($r('sys.float.ohos_id_text_size_headline8')), 142 getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)), 143 fontWeight: FontWeight.Bold, 144 }; 145 this.titleContainerWidth = calContainerWidth(containerWidth, options, 146 Constants.CROSS_LINE_RATIO, this.getUIContext().getMeasureUtils()); 147 console.info(TAG, `onSizeChange titleContainerWidth: ${this.titleContainerWidth}`); 148 }) 149 } 150 Row() { 151 Flex({ justifyContent: FlexAlign.Center }) { 152 Text() { 153 Span(permission.reason) 154 } 155 .fontSize(Constants.DIALOG_DESP_FONT_SIZE) 156 .fontWeight(FontWeight.Regular) 157 .fontColor($r('app.color.text_primary')) 158 .lineHeight(Constants.DIALOG_DESP_LINE_HEIGHT) 159 .margin({ 160 top: Constants.DIALOG_DESP_MARGIN_TOP, 161 left: Constants.DIALOG_DESP_MARGIN_LEFT, 162 right: Constants.DIALOG_DESP_MARGIN_RIGHT, 163 bottom: Constants.DIALOG_DESP_MARGIN_BOTTOM 164 }) 165 } 166 } 167 Row() { 168 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 169 Button($r('app.string.BAN')) 170 .onClick(async (): Promise<void> => { 171 await this.enableNotification(false); 172 }) 173 .customizeButton() 174 Divider() 175 .color($r('app.color.comp_divider')) 176 .vertical(true) 177 .height(Constants.DIVIDER_HEIGHT) 178 .strokeWidth(Constants.DIVIDER_WIDTH) 179 .margin({left: Constants.BUTTON_LEFT, right: Constants.BUTTON_RIGHT}) 180 Button($r('app.string.ALLOW')) 181 .onClick(async (): Promise<void> => { 182 await this.enableNotification(true); 183 }) 184 .customizeButton() 185 } 186 .margin({ left: Constants.BUTTON_MARGIN_LEFT, right: Constants.BUTTON_MARGIN_RIGHT }) 187 } 188 } 189 } 190 } 191 .borderRadius(Constants.DIALOG_PRIVACY_BORDER_RADIUS) 192 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) 193 .width(Constants.FULL_WIDTH) 194 .padding({ bottom: Constants.DIALOG_PADDING_BOTTOM }) 195 .clip(true) 196 } 197 } 198 .margin({ 199 left: this.isBottomPopover ? Constants.DIALOG_MARGIN_VERTICAL : Constants.DIALOG_MARGIN, 200 right: this.isBottomPopover ? Constants.DIALOG_MARGIN_VERTICAL : Constants.DIALOG_MARGIN, 201 bottom: this.isBottomPopover ? this.naviHeight : 0 202 }) 203 .constraintSize({ 204 maxHeight: Constants.MAXIMUM_HEADER_HEIGHT, 205 maxWidth: Constants.MAX_DIALOG_WIDTH 206 }) 207 } 208 209 async updateApplicationName(bundleName: string): Promise<void> { 210 console.info(TAG, `updateApplicationName bundleName: ${bundleName}`); 211 try { 212 let bundleFlags = bundleResourceManager.ResourceFlag.GET_RESOURCE_INFO_ALL; 213 let resourceInfo = bundleResourceManager.getBundleResourceInfo(bundleName, bundleFlags); 214 console.info(TAG, `applicationName name : ${JSON.stringify(resourceInfo.label)}`); 215 let appName = resourceInfo.label; 216 this.appName = titleTrim(appName); 217 console.info(TAG, `hap label: ${this.appName}`); 218 } catch (err) { 219 console.error(TAG, `applicationName error : ${err?.code}`); 220 } 221 } 222 223 updateAvoidWindow(): void { 224 let type = window.AvoidAreaType.TYPE_SYSTEM; 225 try { 226 this.dialog?.extensionWindow.on('avoidAreaChange', (data): void => { 227 if (data.type == window.AvoidAreaType.TYPE_SYSTEM) { 228 console.info(TAG, `avoidAreaChange: ${JSON.stringify(data)}`); 229 this.naviHeight = data.area.bottomRect.height; 230 } 231 }); 232 let avoidArea = this.dialog?.extensionWindow.getWindowAvoidArea(type); 233 if (avoidArea != undefined){ 234 console.info(TAG, `avoidArea: ${JSON.stringify(avoidArea)}`); 235 this.naviHeight = avoidArea.bottomRect.height; 236 } 237 } catch (err) { 238 console.error(TAG, `Failed to obtain the area. Cause: ${err?.code}`); 239 } 240 } 241 242 async updateIsBottomPopover(): Promise<void> { 243 let dis = display.getDefaultDisplaySync(); 244 let isVertical = dis.width <= dis.height; 245 try { 246 if (display.isFoldable()) { 247 let foldStatus = display.getFoldStatus(); 248 if (foldStatus == display.FoldStatus.FOLD_STATUS_EXPANDED || 249 foldStatus == display.FoldStatus.FOLD_STATUS_HALF_FOLDED) { 250 this.isBottomPopover = false; 251 return; 252 } 253 } 254 } catch (err) { 255 console.error(TAG, 'Failed to get the device foldable status. Code: ${err?.code}'); 256 } 257 258 // read ccm configs 259 let isBottomPopoverTemp = false; 260 try { 261 let filePaths = await configPolicy.getCfgFiles(Constants.CCM_CONFIG_PATH); 262 for (let i = 0; i < filePaths.length; i++) { 263 let res = fs.accessSync(filePaths[i]); 264 if (res) { 265 let fileContent = fs.readTextSync(filePaths[i]); 266 let config: NotificationConfig = JSON.parse(fileContent); 267 if (config.notificationAuthorizationWindow != undefined) { 268 let windowConfig: NotificationAuthorizationWindow = config.notificationAuthorizationWindow; 269 if (windowConfig.isBottomPopover != undefined) { 270 isBottomPopoverTemp = windowConfig.isBottomPopover; 271 } 272 } 273 } 274 } 275 } catch (error) { 276 console.log(TAG, 'Failed get ccm files, Cause: ${err?.code}'); 277 } 278 this.isBottomPopover = isBottomPopoverTemp && isVertical; 279 } 280 281 async updateStatus(): Promise<void> { 282 let bundleNameObj = this.dialog?.want.parameters?.bundleName; 283 let bundleName = bundleNameObj ? bundleNameObj.toString() : ''; 284 await this.updateApplicationName(bundleName); 285 await this.updateIsBottomPopover(); 286 } 287 288 async updateOnPageShow(): Promise<void> { 289 if (this.isUpdate > 0) { 290 await this.updateStatus(); 291 } 292 } 293 294 async aboutToAppear(): Promise<void> { 295 this.dialog = storage.get('dialog') as EnableNotificationDialog; 296 this.session = storage.get('session') as UIExtensionContentSession; 297 this.updateAvoidWindow(); 298 try { 299 await this.updateStatus(); 300 } catch (err) { 301 console.error(TAG, `aboutToAppear error : ${err?.code}`); 302 await this.dialog?.destroyException(); 303 await this.session?.terminateSelf(); 304 } 305 } 306 307 async registerFoldableCallback(): Promise<void> { 308 let callback: Callback<display.FoldDisplayMode> = async (data: display.FoldDisplayMode) => { 309 try { 310 let win = this.dialog.window; 311 let dis = display.getDefaultDisplaySync(); 312 await win.moveWindowTo(0, 0); 313 await win.resize(dis.width, dis.height); 314 await this.updateStatus(); 315 } catch (err) { 316 console.error(TAG, 'Failed to touch callback. Code: ${err?.code}'); 317 } 318 }; 319 try { 320 display.on('foldDisplayModeChange', callback); 321 } catch (err) { 322 console.error(TAG, 'Failed to register callback. Code: ${err?.code}'); 323 } 324 } 325 326 async aboutToDisappear(): Promise<void> { 327 console.info(TAG, `aboutToDisappear`); 328 this.session?.terminateSelf(); 329 } 330 331 async enableNotification(enabled: boolean): Promise<void> { 332 console.info(TAG, `NotificationDialog enableNotification: ${enabled}`); 333 try { 334 await this.dialog?.publishButtonClickedEvent(enabled); 335 this.clicked = true; 336 } catch (err) { 337 console.error(TAG, `NotificationDialog enable error, code is ${err?.code}`); 338 await this.dialog?.destroyException(); 339 } finally { 340 await this.dialog?.subWindow?.destroyWindow(); 341 this.session?.terminateSelf(); 342 } 343 } 344} 345 346interface NotificationConfig { 347 notificationAuthorizationWindow: NotificationAuthorizationWindow; 348} 349 350interface NotificationAuthorizationWindow { 351 isBottomPopover: boolean; 352} 353