• 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  @State maxHeight: string|number = '85%';
97
98  build() {
99    Row(){
100      Flex({ justifyContent: FlexAlign.Center, alignItems: this.isBottomPopover ? ItemAlign.End : ItemAlign.Center }) {
101        Column() {
102          Scroll() {
103            Column() {
104              Row() {
105                Image(permission.icon)
106                .width(Constants.DIALOG_ICON_WIDTH)
107                .height(Constants.DIALOG_ICON_HEIGHT)
108                .margin({
109                  top: Constants.DIALOG_ICON_MARGIN_TOP
110                })
111              }
112              Row() {
113                Flex({ justifyContent: FlexAlign.Center }) {
114                  Text($r('app.string.group_label_notification', this.appName))
115                  .fontSize($r('sys.float.ohos_id_text_size_headline8'))
116                  .fontColor($r('app.color.text_primary'))
117                  .fontWeight(FontWeight.Bold)
118                  .minFontSize(
119                    getLimitFontSize(Constants.TITLE_MIN_FONT_SIZE,
120                      getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX))
121                  )
122                  .maxFontSize(
123                    getLimitFontSize(sourceToVp($r('sys.float.ohos_id_text_size_headline8')),
124                      getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX))
125                  )
126                  .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST)
127                  .maxLines(2)
128                  .textOverflow({overflow: TextOverflow.Ellipsis})
129                  .width(this.titleContainerWidth)
130                  .textAlign(TextAlign.Center)
131                }
132                .margin({
133                  top: Constants.DIALOG_REQ_MARGIN_TOP,
134                  bottom:Constants.DIALOG_REQ_MARGIN_BUTTOM,
135                  left: Constants.DIALOG_REQ_MARGIN_LEFT,
136                  right: Constants.DIALOG_REQ_MARGIN_RIGHT
137                })
138                .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
139                  let containerWidth = newValue.width as number;
140                  let options: MeasureOptions = {
141                    textContent: $r('app.string.group_label_notification', this.appName),
142                    fontSize: getLimitFontSize(sourceToVp($r('sys.float.ohos_id_text_size_headline8')),
143                      getFontSizeScale(getContext(this) as common.UIAbilityContext, Constants.FONT_SCALE_MAX)),
144                    fontWeight: FontWeight.Bold,
145                  };
146                  this.titleContainerWidth = calContainerWidth(containerWidth, options,
147                    Constants.CROSS_LINE_RATIO, this.getUIContext().getMeasureUtils());
148                  console.info(TAG, `onSizeChange titleContainerWidth: ${this.titleContainerWidth}`);
149                })
150              }
151              Row() {
152                Flex({ justifyContent: FlexAlign.Center }) {
153                  Text() {
154                    Span(permission.reason)
155                  }
156                  .fontSize(Constants.DIALOG_DESP_FONT_SIZE)
157                  .fontWeight(FontWeight.Regular)
158                  .fontColor($r('app.color.text_primary'))
159                  .lineHeight(Constants.DIALOG_DESP_LINE_HEIGHT)
160                }
161                .margin({
162                  top: Constants.DIALOG_DESP_MARGIN_TOP,
163                  left: Constants.DIALOG_DESP_MARGIN_LEFT,
164                  right: Constants.DIALOG_DESP_MARGIN_RIGHT,
165                  bottom: Constants.DIALOG_DESP_MARGIN_BOTTOM
166                })
167              }
168              Row() {
169                Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
170                  Button($r('app.string.BAN'))
171                    .onClick(async (): Promise<void> => {
172                    await this.enableNotification(false);
173                    })
174                    .customizeButton()
175                  Divider()
176                    .color($r('app.color.comp_divider'))
177                    .vertical(true)
178                    .height(Constants.DIVIDER_HEIGHT)
179                    .strokeWidth(Constants.DIVIDER_WIDTH)
180                    .margin({left: Constants.BUTTON_LEFT, right: Constants.BUTTON_RIGHT})
181                  Button($r('app.string.ALLOW'))
182                    .onClick(async (): Promise<void> => {
183                      await this.enableNotification(true);
184                    })
185                    .customizeButton()
186                }
187                .margin({ left: Constants.BUTTON_MARGIN_LEFT, right: Constants.BUTTON_MARGIN_RIGHT })
188              }
189            }
190          }
191        }
192        .borderRadius(Constants.DIALOG_PRIVACY_BORDER_RADIUS)
193        .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK)
194        .width(Constants.FULL_WIDTH)
195        .padding({ bottom: Constants.DIALOG_PADDING_BOTTOM })
196        .clip(true)
197      }
198    }
199    .margin({
200      left: this.isBottomPopover ? Constants.DIALOG_MARGIN_VERTICAL : Constants.DIALOG_MARGIN,
201      right: this.isBottomPopover ? Constants.DIALOG_MARGIN_VERTICAL : Constants.DIALOG_MARGIN,
202      bottom: this.isBottomPopover ? this.naviHeight : 0
203    })
204    .constraintSize({
205      maxHeight: this.maxHeight,
206      maxWidth: Constants.MAX_DIALOG_WIDTH
207    })
208  }
209
210  async updateApplicationName(bundleName: string): Promise<void> {
211    console.info(TAG, `updateApplicationName bundleName: ${bundleName}`);
212    try {
213      let bundleFlags = bundleResourceManager.ResourceFlag.GET_RESOURCE_INFO_ALL;
214      let resourceInfo = bundleResourceManager.getBundleResourceInfo(bundleName, bundleFlags);
215      console.info(TAG, `applicationName name : ${JSON.stringify(resourceInfo.label)}`);
216      let appName = resourceInfo.label;
217      this.appName = titleTrim(appName);
218      console.info(TAG, `hap label: ${this.appName}`);
219    } catch (err) {
220      console.error(TAG, `applicationName error : ${err?.code}`);
221    }
222  }
223
224  updateAvoidWindow(): void {
225    let type = window.AvoidAreaType.TYPE_SYSTEM;
226    try {
227      this.dialog?.extensionWindow.on('avoidAreaChange', (data): void => {
228        if (data.type == window.AvoidAreaType.TYPE_SYSTEM) {
229          console.info(TAG, `avoidAreaChange: ${JSON.stringify(data)}`);
230          this.naviHeight = data.area.bottomRect.height;
231        }
232      });
233      let avoidArea = this.dialog?.extensionWindow.getWindowAvoidArea(type);
234      if (avoidArea != undefined){
235        console.info(TAG, `avoidArea: ${JSON.stringify(avoidArea)}`);
236        this.naviHeight = avoidArea.bottomRect.height;
237      }
238    } catch (err) {
239      console.error(TAG, `Failed to obtain the area. Cause: ${err?.code}`);
240    }
241  }
242
243  updateSubWindowSize(): void {
244    try {
245      this.dialog?.extensionWindow.on('windowSizeChange', (data):void => {
246        let windowRect = this.dialog?.extensionWindow.properties?.uiExtensionHostWindowProxyRect;
247        console.info(TAG, `windowSizeChange event, size = ${windowRect?.left}-${windowRect?.top}-${windowRect?.width}-${windowRect?.height}`);
248        if (!this.dialog?.initSubWindowSize && windowRect.width > 0 && windowRect.height > 0) {
249          console.info(TAG, `windowSizeChange first time update`);
250          this.dialog?.subWindow?.moveWindowToGlobal(windowRect?.left, windowRect?.top);
251          this.dialog?.subWindow?.resize(windowRect?.width, windowRect?.height);
252          this.dialog.initSubWindowSize = true;
253        }
254        this.updatemaxHeight();
255      });
256    } catch (err) {
257      console.error(TAG, `updateSubWindowSize error. Cause: ${err?.code}`);
258    }
259  }
260
261  updatemaxHeight(): void {
262    try {
263      let windowRect = this.dialog?.extensionWindow.properties?.uiExtensionHostWindowProxyRect;
264      let height = windowRect?.height;
265      let navigationArea = this.dialog?.extensionWindow.getWindowAvoidArea(
266        window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
267      let navigationHeight = navigationArea?.bottomRect?.height;
268      let systemArea = this.dialog?.extensionWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
269      let statusBarHeight = systemArea?.topRect?.height;
270      console.info(TAG, `windowHeight ${windowRect?.height} navigationHeight ${navigationHeight} statusBarHeight ${statusBarHeight} `);
271      if (height > 0 && (height - navigationHeight - statusBarHeight) > 0 ) {
272        this.maxHeight = px2vp((height - navigationHeight - statusBarHeight) * 0.9);
273      }
274    } catch (err) {
275      console.error(TAG, `updatemaxHeight error. Cause: ${err?.code}`);
276    }
277  }
278
279  async updateIsBottomPopover(): Promise<void> {
280    let dis = display.getDefaultDisplaySync();
281    let isVertical = dis.width <= dis.height;
282    try {
283      if (display.isFoldable()) {
284        let foldStatus = display.getFoldStatus();
285        if (foldStatus == display.FoldStatus.FOLD_STATUS_EXPANDED ||
286          foldStatus == display.FoldStatus.FOLD_STATUS_HALF_FOLDED) {
287            this.isBottomPopover = false;
288            return;
289        }
290      }
291    } catch (err) {
292      console.error(TAG, 'Failed to get the device foldable status. Code: ${err?.code}');
293    }
294
295    // read ccm configs
296    let isBottomPopoverTemp = false;
297    try {
298      let filePaths = await configPolicy.getCfgFiles(Constants.CCM_CONFIG_PATH);
299      for (let i = 0; i < filePaths.length; i++) {
300        let res = fs.accessSync(filePaths[i]);
301        if (res) {
302          let fileContent = fs.readTextSync(filePaths[i]);
303          let config: NotificationConfig = JSON.parse(fileContent);
304          if (config.notificationAuthorizationWindow != undefined) {
305            let windowConfig: NotificationAuthorizationWindow = config.notificationAuthorizationWindow;
306            if (windowConfig.isBottomPopover != undefined) {
307              isBottomPopoverTemp = windowConfig.isBottomPopover;
308            }
309          }
310        }
311      }
312    } catch (error) {
313      console.log(TAG, 'Failed get ccm files, Cause: ${err?.code}');
314    }
315    this.isBottomPopover = isBottomPopoverTemp && isVertical;
316  }
317
318  async updateStatus(): Promise<void> {
319    let bundleNameObj = this.dialog?.want.parameters?.bundleName;
320    let bundleName = bundleNameObj ? bundleNameObj.toString() : '';
321    await this.updateApplicationName(bundleName);
322    await this.updateIsBottomPopover();
323  }
324
325  async updateOnPageShow(): Promise<void> {
326    if (this.isUpdate > 0) {
327      await this.updateStatus();
328    }
329  }
330
331  async aboutToAppear(): Promise<void> {
332    this.dialog = storage.get('dialog') as EnableNotificationDialog;
333    this.session = storage.get('session') as UIExtensionContentSession;
334    this.updateAvoidWindow();
335    this.updateSubWindowSize();
336    this.updatemaxHeight();
337    try {
338      await this.updateStatus();
339    } catch (err) {
340      console.error(TAG, `aboutToAppear error : ${err?.code}`);
341      await this.dialog?.destroyException();
342      await this.session?.terminateSelf();
343    }
344  }
345
346  async registerFoldableCallback(): Promise<void> {
347    let callback: Callback<display.FoldDisplayMode> = async (data: display.FoldDisplayMode) => {
348      try {
349        let win = this.dialog.window;
350        let dis = display.getDefaultDisplaySync();
351        await win.moveWindowTo(0, 0);
352        await win.resize(dis.width, dis.height);
353        await this.updateStatus();
354      } catch (err) {
355        console.error(TAG, 'Failed to touch callback. Code: ${err?.code}');
356      }
357    };
358    try {
359      display.on('foldDisplayModeChange', callback);
360    } catch (err) {
361      console.error(TAG, 'Failed to register callback. Code: ${err?.code}');
362    }
363  }
364
365  async aboutToDisappear(): Promise<void> {
366    console.info(TAG, `aboutToDisappear`);
367    this.session?.terminateSelf();
368  }
369
370  async enableNotification(enabled: boolean): Promise<void> {
371    console.info(TAG, `NotificationDialog enableNotification: ${enabled}`);
372    try {
373      await this.dialog?.publishButtonClickedEvent(enabled);
374      this.clicked = true;
375    } catch (err) {
376      console.error(TAG, `NotificationDialog enable error, code is ${err?.code}`);
377      await this.dialog?.destroyException();
378    } finally {
379      await this.dialog?.subWindow?.destroyWindow();
380      this.session?.terminateSelf();
381    }
382  }
383}
384
385interface NotificationConfig {
386  notificationAuthorizationWindow: NotificationAuthorizationWindow;
387}
388
389interface NotificationAuthorizationWindow {
390  isBottomPopover: boolean;
391}
392