• 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 */
15import bundleManager from '@ohos.bundle.bundleManager';
16import Constants from '../common/utils/constant';
17import rpc from '@ohos.rpc';
18import window from '@ohos.window';
19import common from '@ohos.app.ability.common';
20import { BusinessError } from '@ohos.base';
21import { CustomContentDialog } from '@ohos.arkui.advanced.Dialog';
22import { Log, getFontSizeScale } from '../common/utils/utils';
23import { Param, WantInfo, DialogInfo } from '../common/model/typedef';
24import { GlobalContext } from '../common/utils/globalContext';
25import bundleResourceManager from '@ohos.bundle.bundleResourceManager';
26import deviceInfo from '@ohos.deviceInfo';
27
28const RESOURCE_TYPE: number = 10003;
29
30@Extend(Text)
31function titleText(isWearable: boolean) {
32  .fontWeight(FontWeight.Bold)
33  .fontColor(isWearable ? '#FFFFFF' : $r('sys.color.font_primary'))
34  .textAlign(TextAlign.Center)
35  .textOverflow({ overflow: TextOverflow.Ellipsis })
36  .maxLines(Constants.SECURITY_HEADER_MAX_LINES)
37}
38
39@Extend(Button)
40function customizeButton() {
41  .padding({
42    left: Constants.PADDING_16,
43    right: Constants.PADDING_16,
44    top: Constants.PADDING_10,
45    bottom: Constants.PADDING_10
46  })
47  .width(Constants.CUSTOMIZE_BUTTON_WIDTH)
48  .margin({ bottom : Constants.MARGIN_12 })
49}
50
51@Extend(Text)
52function customizeButtonText() {
53  .minFontSize(Constants.CUSTOMIZE_BUTTON_TEXT_MIN_SIZE)
54  .maxFontSize(Constants.CUSTOMIZE_BUTTON_TEXT_MAX_SIZE)
55  .textAlign(TextAlign.Center)
56  .fontSize(Constants.CUSTOMIZE_BUTTON_TEXT_MAX_SIZE)
57  .fontWeight(FontWeight.Medium)
58  .textOverflow({ overflow: TextOverflow.MARQUEE })
59}
60
61@Builder
62function dialogTitle(index: number, securityParams: Array<Param>, isWearable: boolean) {
63  Column() {
64    if (getFontSizeScale()) {
65      Text(securityParams[index].label)
66        .titleText(isWearable)
67        .fontSize($r('sys.float.Title_S'))
68    } else {
69      Text(securityParams[index].label)
70        .titleText(isWearable)
71        .minFontSize(Constants.TEXT_MIDDLE_FONT_SIZE)
72        .maxFontSize($r('sys.float.Title_S'))
73        .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST)
74    }
75  }
76  .constraintSize({ minHeight: Constants.HEADLINE_HEIGHT })
77  .justifyContent(FlexAlign.Center)
78  .padding({
79    top: Constants.DEFAULT_PADDING_TOP,
80    bottom: Constants.DEFAULT_PADDING_BOTTOM,
81  })
82}
83
84@Builder
85function dialogContentsArea(params: DialogInfo) {
86  Column() {
87    Column() {
88      Row() {
89        SymbolGlyph($r('sys.symbol.security_shield'))
90          .fontSize(Constants.FONT_SIZE_28)
91          .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_COLOR)
92          .fontColor(['#E5000000', '#0A59F7'])
93      }
94      .width(Constants.SECURITY_ICON_WIDTH)
95      .height(Constants.SECURITY_ICON_HEIGHT)
96      .justifyContent(FlexAlign.Center)
97      .pixelRound({ start: PixelRoundCalcPolicy.NO_FORCE_ROUND, end: PixelRoundCalcPolicy.NO_FORCE_ROUND })
98      .border({
99        width: Constants.BORDER_WIDTH_1,
100        color: $r('app.color.icon_border'),
101        radius: Constants.SECURITY_ICON_WIDTH * 14 / 54
102      })
103      if (params.index === 1) {
104        Image(params.securityParams[params.index].icon)
105          .width(Constants.IMAGE_LENGTH_20)
106          .height(Constants.IMAGE_LENGTH_20)
107          .syncLoad(true)
108          .position({ x: Constants.IMAGE_POSITION_28, y: Constants.IMAGE_POSITION_28 })
109          .border({
110            width: Constants.BORDER_WIDTH_1,
111            color: $r('app.color.icon_border'),
112            radius: Constants.IMAGE_LENGTH_20 * 14 / 54
113          })
114      } else {
115        Row() {
116          SymbolGlyph($r('sys.symbol.local_fill'))
117            .fontSize(Constants.FONT_SIZE_12)
118            .fontColor([Color.White])
119        }
120        .width(Constants.IMAGE_LENGTH_20)
121        .height(Constants.IMAGE_LENGTH_20)
122        .justifyContent(FlexAlign.Center)
123        .backgroundColor($r('app.color.local_background_color'))
124        .position({ x: Constants.IMAGE_POSITION_28, y: Constants.IMAGE_POSITION_28 })
125        .border({
126          width: Constants.BORDER_WIDTH_1,
127          color: $r('app.color.icon_border'),
128          radius: Constants.IMAGE_LENGTH_20 * 14 / 54
129        })
130      }
131    }
132    .pixelRound({ start: PixelRoundCalcPolicy.NO_FORCE_ROUND, end: PixelRoundCalcPolicy.NO_FORCE_ROUND })
133    .backgroundColor($r('app.color.icon_bg'))
134    .borderRadius(Constants.SECURITY_ICON_WIDTH * 14 / 54)
135    .margin(params.isWearable ? { top: Constants.MARGIN_12 } : {})
136    Column() { // content
137      dialogTitle(params.index, params.securityParams, params.isWearable);
138      Text($r(
139        params.securityParams[params.index].description,
140        params.appName,
141        params.appIndex === 0 ? '' : String(params.appIndex)
142      ))
143        .textAlign(params.isWearable ? TextAlign.Center : TextAlign.Start)
144        .fontColor($r('sys.color.font_primary'))
145        .fontSize($r('sys.float.Body_L'))
146        .maxFontScale(Constants.DIALOG_TEXT_MAX_SCALE)
147        .margin(params.isWearable ? {
148          left: Constants.MARGIN_26,
149          right: Constants.MARGIN_26,
150          bottom: Constants.MARGIN_12
151        } : {})
152    }
153  }
154  .clip(true)
155}
156
157@CustomDialog
158struct CustomDialogWearable {
159  cancel?: () => void;
160  confirm?: () => void;
161  dialogControllerForWearable: CustomDialogController;
162  @Link index: number;
163  @Link appName: ResourceStr;
164  @Link securityParams: Array<Param>;
165  @Link isWearable: boolean;
166  @Link appIndex: number;
167
168  build() {
169    Scroll() {
170      Column() {
171        dialogContentsArea({
172          index: this.index,
173          securityParams: this.securityParams,
174          appName: this.appName,
175          isWearable: this.isWearable,
176          appIndex: this.appIndex
177        })
178        Button({ type: ButtonType.Capsule, stateEffect: true }) {
179          Text($r('app.string.allow'))
180            .customizeButtonText()
181            .fontColor('#FFFFFF')
182        }
183        .customizeButton()
184        .backgroundColor('#1F71FF')
185        .onClick(() => {
186          Log.info('Allow click start.');
187          this.dialogControllerForWearable?.close();
188          if (this.confirm) {
189            this.confirm();
190          }
191        })
192        Button({ type: ButtonType.Capsule, stateEffect: true }) {
193          Text($r('app.string.cancel'))
194            .customizeButtonText()
195            .fontColor('#5EA1FF')
196        }
197        .customizeButton()
198        .backgroundColor('#405EA1FF')
199        .onClick(() => {
200          Log.info('cancel click start.');
201          this.dialogControllerForWearable?.close();
202          if (this.cancel) {
203            this.cancel();
204          }
205        })
206      }
207    }
208    .edgeEffect(EdgeEffect.Spring)
209    .backgroundColor(Color.Black)
210    .width(Constants.FULL_WIDTH)
211    .height(Constants.FULL_HEIGHT)
212  }
213}
214
215@Entry({ useSharedStorage: true })
216@Component
217struct SecurityDialog {
218  private context = this.getUIContext().getHostContext() as common.ServiceExtensionContext;
219  @LocalStorageLink('want') want: WantInfo = new WantInfo([]);
220  @LocalStorageLink('win') win: window.Window = {} as window.Window;
221  @State appName: ResourceStr = 'Application';
222  @State appIndex: number = 0;
223  @State index: number = 0;
224  @State scrollBarWidth: number = Constants.SCROLL_BAR_WIDTH_DEFAULT;
225  @State isWearable: boolean = false;
226
227  @State securityParams : Array<Param> = [
228    new Param(
229      $r('app.media.ic_location'), $r('app.string.SecurityTitle_location'), 'app.string.SecurityText_location'
230    ),
231    new Param(
232      $r('app.media.rawfile'), $r('app.string.SecurityTitle_mediaFiles'), 'app.string.SecurityText_mediaFiles'
233    )
234  ]
235
236  dialogControllerForWearable: CustomDialogController | null = new CustomDialogController({
237    builder: CustomDialogWearable({
238      cancel: () => {
239        this.loadCancel();
240      },
241      confirm: () => {
242        this.onAccept();
243      },
244      index: $index,
245      appName: $appName,
246      isWearable: $isWearable,
247      securityParams: $securityParams,
248      appIndex: $appIndex
249    }),
250    customStyle: true,
251    cancel: () => {
252      this.loadCancel();
253    },
254  })
255
256  dialogController: CustomDialogController | null = new CustomDialogController({
257    builder: CustomContentDialog({
258      contentBuilder: () => {
259        this.buildContent();
260      },
261      contentAreaPadding: { right: 0 },
262      buttons: [
263        {
264          value: $r('app.string.cancel'),
265          buttonStyle: ButtonStyleMode.TEXTUAL,
266          action: () => {
267            this.dialogController?.close();
268            this.loadCancel();
269          }
270        },
271        {
272          value: $r('app.string.allow'),
273          buttonStyle: ButtonStyleMode.TEXTUAL,
274          action: () => {
275            this.dialogController?.close();
276            this.destruction();
277          }
278        }
279      ],
280    }),
281    autoCancel: false,
282    cancel: () => {
283      this.loadCancel();
284    },
285  });
286
287  @Builder
288  buildContent(): void {
289    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
290      Scroll() {
291        dialogContentsArea({
292          index: this.index,
293          securityParams: this.securityParams,
294          appName: this.appName,
295          isWearable: this.isWearable,
296          appIndex: this.appIndex
297        })
298      }
299      .padding({ left: Constants.PADDING_24, right: Constants.PADDING_24 })
300      .margin({ top: Constants.MARGIN_24 })
301      .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: false })
302      .scrollBarWidth(this.scrollBarWidth)
303      .onScrollStart(() => {
304        this.scrollBarWidth = Constants.SCROLL_BAR_WIDTH_ACTIVE;
305      })
306      .onScrollStop(() => {
307        this.scrollBarWidth = Constants.SCROLL_BAR_WIDTH_DEFAULT;
308      })
309    }
310  }
311
312  build() {}
313
314  aboutToAppear() {
315    Log.info('onAboutToAppear.');
316    this.GetAppName();
317    this.index = this.want.parameters['ohos.user.security.type'];
318    if (deviceInfo.deviceType === 'wearable') {
319      this.dialogControllerForWearable?.open();
320      this.isWearable = true;
321    } else {
322      this.dialogController?.open();
323      this.isWearable = false;
324    }
325  }
326
327  aboutToDisappear() {
328    Log.info('aboutToDisappear.');
329    this.dialogController = null;
330    this.dialogControllerForWearable = null;
331  }
332
333  loadCancel() {
334    Log.info('Callback when the first button is clicked.');
335    this.win?.destroyWindow();
336    let dialogSet: Set<String> = GlobalContext.load('dialogSet');
337    let callerToken: number = this.want.parameters['ohos.caller.uid'];
338    let windId: number = this.want.parameters['ohos.ability.params.windowId'];
339    let token: String = String(callerToken) + '_' + String(windId);
340    dialogSet.delete(token);
341    GlobalContext.store('dialogSet', dialogSet);
342    if (dialogSet.size === 0) {
343      this.context.terminateSelf();
344    }
345  }
346
347  onAccept() {
348    Log.info('Callback when the second button is clicked.');
349    this.destruction();
350  }
351
352  GetAppName() {
353    let uid: number = this.want.parameters['ohos.caller.uid'];
354    try {
355      bundleManager.getAppCloneIdentity(uid).then(cloneInfo => {
356        Log.info(`getAppCloneIdentity: ${JSON.stringify(cloneInfo)}.`);
357        this.appIndex = cloneInfo.appIndex;
358        this.getSelfName(cloneInfo);
359      }).catch((err: BusinessError) => {
360        Log.error(`getAppCloneIdentity failed: ${JSON.stringify(err)}.`);
361      })
362    } catch (err) {
363      Log.error(`get appName failed: ${JSON.stringify(err)}.`);
364    };
365  }
366
367  getSelfName(cloneInfo: bundleManager.AppCloneIdentity) {
368    try {
369      bundleManager.getApplicationInfo(cloneInfo.bundleName, bundleManager.ApplicationFlag.GET_APPLICATION_INFO_DEFAULT)
370        .then(data => {
371          data.labelResource.params = [];
372          data.labelResource.type = RESOURCE_TYPE;
373          this.appName = data.labelResource;
374        }).catch((error: BusinessError) => {
375          Log.error(`getApplicationInfo failed. err is ${JSON.stringify(error)}.`);
376        });
377    } catch (err) {
378      Log.error(`getSelfName failed. err is ${JSON.stringify(err)}.`);
379    };
380  }
381
382  getCloneName(cloneInfo: bundleManager.AppCloneIdentity) {
383    try {
384      let resourceFlag = bundleResourceManager.ResourceFlag.GET_RESOURCE_INFO_ALL;
385      let resourceInfo =
386        bundleResourceManager.getBundleResourceInfo(cloneInfo.bundleName, resourceFlag, cloneInfo.appIndex);
387      this.appName = resourceInfo?.label;
388    } catch (err) {
389      Log.error(`getCloneName failed. err is ${JSON.stringify(err)}.`);
390    };
391  }
392
393  destruction() {
394    let option = new rpc.MessageOption();
395    let data = new rpc.MessageSequence();
396    let reply = new rpc.MessageSequence();
397    Promise.all([
398      data.writeInterfaceToken(Constants.SEC_COMP_DIALOG_CALLBACK),
399      data.writeInt(0)
400    ]).then(() => {
401      let proxy = this.want.parameters['ohos.ability.params.callback'].value as rpc.RemoteObject;
402      if (proxy != undefined) {
403        proxy.sendMessageRequest(Constants.RESULT_CODE, data, reply, option);
404      }
405    }).catch(() => {
406      Log.error('write result failed!');
407    }).finally(() => {
408      data.reclaim();
409      reply.reclaim();
410      this.loadCancel();
411    })
412  }
413}
414