• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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