• 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
28let storage = LocalStorage.getShared();
29const RESOURCE_TYPE: number = 10003;
30
31@Extend(Text)
32function titleText(isWearable: boolean) {
33  .fontWeight(FontWeight.Bold)
34  .fontColor(isWearable ? '#FFFFFF' : $r('sys.color.font_primary'))
35  .textAlign(TextAlign.Center)
36  .textOverflow({ overflow: TextOverflow.Ellipsis })
37  .maxLines(Constants.SECURITY_HEADER_MAX_LINES)
38}
39
40@Extend(Button)
41function customizeButton() {
42  .padding({
43    left: Constants.PADDING_16,
44    right: Constants.PADDING_16,
45    top: Constants.PADDING_10,
46    bottom: Constants.PADDING_10
47  })
48  .width(Constants.CUSTOMIZE_BUTTON_WIDTH)
49  .margin({ bottom : Constants.MARGIN_12 })
50}
51
52@Extend(Text)
53function customizeButtonText() {
54  .minFontSize(Constants.CUSTOMIZE_BUTTON_TEXT_MIN_SIZE)
55  .maxFontSize(Constants.CUSTOMIZE_BUTTON_TEXT_MAX_SIZE)
56  .textAlign(TextAlign.Center)
57  .fontSize(Constants.CUSTOMIZE_BUTTON_TEXT_MAX_SIZE)
58  .fontWeight(FontWeight.Medium)
59  .textOverflow({ overflow: TextOverflow.MARQUEE })
60}
61
62@Builder
63function dialogTitle(index: number, securityParams: Array<Param>, isWearable: boolean) {
64  Column() {
65    if (getFontSizeScale()) {
66      Text(securityParams[index].label)
67        .titleText(isWearable)
68        .fontSize($r('sys.float.Title_S'))
69    } else {
70      Text(securityParams[index].label)
71        .titleText(isWearable)
72        .minFontSize(Constants.TEXT_MIDDLE_FONT_SIZE)
73        .maxFontSize($r('sys.float.Title_S'))
74        .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST)
75    }
76  }
77  .constraintSize({ minHeight: Constants.HEADLINE_HEIGHT })
78  .justifyContent(FlexAlign.Center)
79  .padding({
80    top: Constants.DEFAULT_PADDING_TOP,
81    bottom: Constants.DEFAULT_PADDING_BOTTOM,
82  })
83}
84
85@Builder
86function dialogContentsArea(params: DialogInfo) {
87  Column() {
88    Column() {
89      Row() {
90        SymbolGlyph($r('sys.symbol.person_shield_fill'))
91          .fontSize(Constants.FONT_SIZE_28)
92          .fontColor([$r('sys.color.brand')])
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(storage)
216@Component
217struct SecurityDialog {
218  private context = getContext(this) 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.onCancel()
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  })
252
253  dialogController: CustomDialogController | null = new CustomDialogController({
254    builder: CustomContentDialog({
255      contentBuilder: () => {
256        this.buildContent();
257      },
258      contentAreaPadding: { right: 0 },
259      buttons: [
260        {
261          value: $r('app.string.cancel'),
262          buttonStyle: ButtonStyleMode.TEXTUAL,
263          action: () => {
264            this.dialogController?.close();
265            this.win.destroyWindow();
266            let dialogSet: Set<number> = GlobalContext.load('dialogSet');
267            let callerToken: number = this.want.parameters['ohos.caller.uid'];
268            dialogSet.delete(callerToken);
269            GlobalContext.store('dialogSet', dialogSet);
270            if (dialogSet.size === 0) {
271              this.context.terminateSelf();
272            }
273          }
274        },
275        {
276          value: $r('app.string.allow'),
277          buttonStyle: ButtonStyleMode.TEXTUAL,
278          action: () => {
279            this.dialogController?.close();
280            this.destruction();
281          }
282        }
283      ],
284    }),
285    autoCancel: false,
286    cancel: () => {
287      this.win.destroyWindow();
288      let dialogSet: Set<number> = GlobalContext.load('dialogSet');
289      let callerToken: number = this.want.parameters['ohos.caller.uid'];
290      dialogSet.delete(callerToken);
291      GlobalContext.store('dialogSet', dialogSet);
292      if (dialogSet.size === 0) {
293        this.context.terminateSelf();
294      }
295    },
296  });
297
298  @Builder
299  buildContent(): void {
300    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
301      Scroll() {
302        dialogContentsArea({
303          index: this.index,
304          securityParams: this.securityParams,
305          appName: this.appName,
306          isWearable: this.isWearable,
307          appIndex: this.appIndex
308        })
309      }
310      .padding({ left: Constants.PADDING_24, right: Constants.PADDING_24 })
311      .margin({ top: Constants.MARGIN_24 })
312      .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: false })
313      .scrollBarWidth(this.scrollBarWidth)
314      .onScrollStart(() => {
315        this.scrollBarWidth = Constants.SCROLL_BAR_WIDTH_ACTIVE;
316      })
317      .onScrollStop(() => {
318        this.scrollBarWidth = Constants.SCROLL_BAR_WIDTH_DEFAULT;
319      })
320    }
321  }
322
323  build() {}
324
325  aboutToAppear() {
326    Log.info('onAboutToAppear.');
327    this.GetAppName();
328    this.index = this.want.parameters['ohos.user.security.type'];
329    if (deviceInfo.deviceType === 'wearable') {
330      this.dialogControllerForWearable?.open();
331      this.isWearable = true;
332    } else {
333      this.dialogController?.open();
334      this.isWearable = false;
335    }
336  }
337
338  aboutToDisappear() {
339    Log.info('aboutToDisappear.');
340    this.dialogController = null;
341    this.dialogControllerForWearable = null;
342  }
343
344  onCancel() {
345    Log.info('Callback when the first button is clicked');
346    this.win.destroyWindow();
347    let dialogSet: Set<number> = GlobalContext.load('dialogSet');
348    let callerToken: number = this.want.parameters['ohos.caller.uid'];
349    dialogSet.delete(callerToken);
350    GlobalContext.store('dialogSet', dialogSet);
351    if (dialogSet.size === 0) {
352      this.context.terminateSelf();
353    }
354  }
355
356  onAccept() {
357    Log.info('Callback when the second button is clicked');
358    this.destruction();
359  }
360
361  GetAppName() {
362    let uid: number = this.want.parameters['ohos.caller.uid'];
363    try {
364      bundleManager.getAppCloneIdentity(uid).then(cloneInfo => {
365        Log.info(`getAppCloneIdentity: ${JSON.stringify(cloneInfo)}`);
366        this.appIndex = cloneInfo.appIndex;
367        this.getSelfName(cloneInfo);
368      }).catch((err: BusinessError) => {
369        Log.error(`getAppCloneIdentity failed: ${JSON.stringify(err)}`);
370      })
371    } catch (err) {
372      Log.error(`get appName failed: ${JSON.stringify(err)}`);
373    }
374  }
375
376  getSelfName(cloneInfo: bundleManager.AppCloneIdentity) {
377    try {
378      bundleManager.getApplicationInfo(cloneInfo.bundleName, bundleManager.ApplicationFlag.GET_APPLICATION_INFO_DEFAULT)
379        .then(data => {
380          data.labelResource.params = [];
381          data.labelResource.type = RESOURCE_TYPE;
382          this.appName = data.labelResource;
383        }).catch((error: BusinessError) => {
384          Log.error('getApplicationInfo failed. err is ' + JSON.stringify(error));
385        });
386    } catch (err) {
387      Log.error('getSelfName failed. err is ' + JSON.stringify(err));
388    }
389  }
390
391  getCloneName(cloneInfo: bundleManager.AppCloneIdentity) {
392    try {
393      let resourceFlag = bundleResourceManager.ResourceFlag.GET_RESOURCE_INFO_ALL;
394      let resourceInfo =
395        bundleResourceManager.getBundleResourceInfo(cloneInfo.bundleName, resourceFlag, cloneInfo.appIndex);
396      this.appName = resourceInfo?.label;
397    } catch (err) {
398      Log.error('getCloneName failed. err is ' + JSON.stringify(err));
399    }
400  }
401
402  destruction() {
403    let option = new rpc.MessageOption();
404    let data = new rpc.MessageSequence();
405    let reply = new rpc.MessageSequence();
406    Promise.all([
407      data.writeInterfaceToken(Constants.SEC_COMP_DIALOG_CALLBACK),
408      data.writeInt(0)
409    ]).then(() => {
410      let proxy = this.want.parameters['ohos.ability.params.callback'].value as rpc.RemoteObject;
411      if (proxy != undefined) {
412        proxy.sendMessageRequest(Constants.RESULT_CODE, data, reply, option);
413      }
414    }).catch(() => {
415      Log.error('write result failed!');
416    }).finally(() => {
417      data.reclaim();
418      reply.reclaim();
419      this.win.destroyWindow();
420      let dialogSet: Set<number> = GlobalContext.load('dialogSet');
421      let callerToken: number = this.want.parameters['ohos.caller.uid'];
422      dialogSet.delete(callerToken);
423      GlobalContext.store('dialogSet', dialogSet);
424      if (dialogSet.size === 0) {
425        this.context.terminateSelf();
426      }
427    })
428  }
429}
430