• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-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 abilityAccessCtrl from '@ohos.abilityAccessCtrl';
17import bundleManager from '@ohos.bundle.bundleManager';
18import rpc from '@ohos.rpc';
19import window from '@ohos.window';
20import common from '@ohos.app.ability.common';
21import display from '@ohos.display';
22import deviceInfo from '@ohos.deviceInfo';
23import { BusinessError } from '@ohos.base';
24import { Log, getPermissionGroup, titleTrim, getPermissionLabel } from '../common/utils/utils';
25import { GroupInfo, wantInfo } from '../common/utils/typedef';
26import { GlobalContext } from '../common/utils/globalContext';
27import Constants from '../common/utils/constant';
28import { showSubpermissionsGrop, userGrantPermissions } from '../common/model/permissionGroup';
29import { LocationCanvas } from '../common/components/location';
30
31@Extend(Button) function customizeButton() {
32  .backgroundColor($r('sys.color.ohos_id_color_dialog_bg'))
33  .fontColor($r('sys.color.ohos_id_color_text_primary_activated'))
34  .fontSize(Constants.TEXT_MIDDLE_FONT_SIZE)
35  .fontWeight(FontWeight.Medium)
36  .height(Constants.BUTTON_HEIGHT)
37  .flexGrow(Constants.FLEX_GROW)
38}
39
40const FUZZY_LOCATION_PERMISSION = 'ohos.permission.APPROXIMATELY_LOCATION';
41const PRECISE_LOCATION_PERMISSION = 'ohos.permission.LOCATION';
42let bottomPopoverTypes = ['default', 'phone'];
43
44let win: window.Window;
45let want: wantInfo;
46let storage = LocalStorage.getShared();
47
48@Entry(storage)
49@Component
50struct dialogPlusPage {
51  @LocalStorageLink('want') want: wantInfo = new wantInfo([]);
52  @LocalStorageLink('win') win: window.Window = {} as window.Window;
53  @State isUpdate: number = -1;
54
55  privacyDialogController: CustomDialogController = new CustomDialogController({
56    builder: PermissionDialog({ isUpdate: $isUpdate }),
57    autoCancel: false,
58    alignment: DialogAlignment.Center,
59    customStyle: true
60  })
61
62  build() {}
63
64  aboutToAppear() {
65    win = this.win
66    want = this.want
67    this.privacyDialogController.open();
68  }
69
70  onPageShow() {
71    this.isUpdate ++;
72  }
73}
74
75@CustomDialog
76struct PermissionDialog {
77  private context = getContext(this) as common.ServiceExtensionContext;
78  @State isBottomPopover: boolean = true;
79  @State count: number = 0;
80  @State result: Array<number> = [];
81  @State accessTokenId: number = 0;
82  @State initStatus: number = Constants.INIT_NEED_TO_WAIT;
83  @State reqPerms: Array<string> = [];
84  @State grantGroups: Array<GroupInfo> = [];
85  @State userFixedFlag: number = 2; // means user fixed
86  @State appName: string = "";
87  @State locationFlag: number = Constants.LOCATION_NONE;
88  @State targetVersion: number = 0;
89  @State reqPermissionDetails: bundleManager.ReqPermissionDetail[] = [];
90  @State naviHeight: number = 0
91  @State refresh: number = 0;
92  @Link @Watch('updateReason') isUpdate: number;
93  controller?: CustomDialogController
94
95  build() {
96    GridRow({ columns: { xs: Constants.XS_COLUMNS, sm: Constants.SM_COLUMNS, md: Constants.MD_COLUMNS, lg: Constants.LG_COLUMNS }, gutter: Constants.DIALOG_GUTTER }) {
97      GridCol({ span: { xs: Constants.XS_SPAN, sm: Constants.SM_SPAN, md: Constants.DIALOG_MD_SPAN, lg: Constants.DIALOG_LG_SPAN },
98        offset: {xs: Constants.XS_OFFSET, sm: Constants.SM_OFFSET, md: Constants.DIALOG_MD_OFFSET, lg: Constants.DIALOG_LG_OFFSET} }) {
99        Flex({ justifyContent: FlexAlign.Center, alignItems: this.isBottomPopover ? ItemAlign.End : ItemAlign.Center }) {
100          Column() {
101            if ((this.initStatus != Constants.INIT_NEED_TO_WAIT) && this.verify()) {
102              Image(this.grantGroups[this.count >= this.grantGroups.length ? this.grantGroups.length - 1 : this.count].icon)
103                .width(Constants.DIALOG_ICON_WIDTH)
104                .height(Constants.DIALOG_ICON_HEIGHT)
105                .fillColor($r("sys.color.ohos_id_color_text_primary"))
106                .margin({
107                  top: Constants.DIALOG_ICON_MARGIN_TOP
108                })
109              if (this.grantGroups.length > 1) {
110                Text(`${this.count + 1} / ${this.grantGroups.length}`)
111                  .fontSize(Constants.DIALOG_LABEL_FONT_SIZE)
112                  .fontColor($r('sys.color.ohos_id_color_text_secondary'))
113                  .lineHeight(Constants.DIALOG_LABEL_LINE_HEIGHT)
114                  .margin({
115                    top: Constants.DIALOG_LABEL_MARGIN_TOP
116                  })
117              }
118              Scroll() {
119                Column() {
120                  Row() {
121                    Flex({ justifyContent: FlexAlign.Start }) {
122                      Text() {
123                        Span($r('app.string.whether_to_allow'))
124                        Span(this.appName)
125                        Span($r("app.string.quotes"))
126                        Span(this.showTitle())
127                      }
128                        .fontSize(Constants.DIALOG_REQ_FONT_SIZE)
129                        .fontColor($r('sys.color.ohos_id_color_text_primary'))
130                        .fontWeight(FontWeight.Medium)
131                        .fontSize(Constants.DIALOG_REQ_FONT_SIZE)
132                        .lineHeight(Constants.DIALOG_REQ_LINE_HEIGHT)
133                        .margin({
134                          top: Constants.DIALOG_REQ_MARGIN_TOP,
135                          left: Constants.DIALOG_REQ_MARGIN_LEFT,
136                          right: Constants.DIALOG_REQ_MARGIN_RIGHT
137                        })
138                    }
139                  }
140
141                  Row() {
142                    Flex({ justifyContent: FlexAlign.Start }) {
143                      Text() {
144                        if (this.showReason()) {
145                          Span($r('app.string.close_exact_position'))
146                        } else {
147                          if (this.grantGroups[this.count >= this.grantGroups.length ? this.grantGroups.length - 1 : this.count].description.length > 0) {
148                            ForEach(this.grantGroups[this.count >= this.grantGroups.length ? this.grantGroups.length - 1 : this.count].description, (item: ResourceStr) => {
149                              Span(item)
150                            })
151                            Span(this.punctuation())
152                          }
153                          Span(this.grantGroups[this.count >= this.grantGroups.length ? this.grantGroups.length - 1 : (this.count + this.refresh - this.refresh)].reason)
154                        }
155                      }
156                        .fontSize(Constants.DIALOG_DESP_FONT_SIZE)
157                        .fontColor($r('sys.color.ohos_id_color_text_secondary'))
158                        .fontSize(Constants.DIALOG_DESP_FONT_SIZE)
159                        .lineHeight(Constants.DIALOG_DESP_LINE_HEIGHT)
160                        .margin({
161                          top: Constants.DIALOG_DESP_MARGIN_TOP,
162                          left: Constants.DIALOG_DESP_MARGIN_LEFT,
163                          right: Constants.DIALOG_DESP_MARGIN_RIGHT,
164                          bottom: Constants.DIALOG_DESP_MARGIN_BOTTOM
165                        })
166                    }
167                  }
168
169                  if (this.locationFlag > Constants.LOCATION_NONE && this.grantGroups[this.count >= this.grantGroups.length ? this.grantGroups.length - 1 : this.count].name === 'LOCATION') {
170                    LocationCanvas({ locationFlag: $locationFlag })
171                  }
172                }
173              }.constraintSize({ maxHeight: Constants.MAXIMUM_HEADER_HEIGHT })
174              Row() {
175                Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
176                  Button($r('app.string.BAN'))
177                    .onClick(() => {
178                      this.privacyCancel(this.grantGroups[this.count], this.accessTokenId, this.reqPerms, this.userFixedFlag)
179                    }).customizeButton()
180                  Divider()
181                    .color($r('sys.color.ohos_id_color_list_separator'))
182                    .vertical(true)
183                    .height(Constants.DIVIDER_HEIGHT)
184                    .opacity(.2)
185                  Button($r('app.string.ALLOW'))
186                    .onClick(() => {
187                      this.privacyAccept(this.grantGroups[this.count], this.accessTokenId, this.reqPerms, this.userFixedFlag)
188                    }).customizeButton()
189                }.margin({ left: Constants.BUTTON_MARGIN_LEFT, right: Constants.BUTTON_MARGIN_RIGHT })
190              }
191            }
192          }
193          .backgroundColor($r('sys.color.ohos_id_color_dialog_bg'))
194          .borderRadius(Constants.DIALOG_PRIVACY_BORDER_RADIUS)
195          .width(Constants.FULL_WIDTH)
196          .padding({ bottom: Constants.DIALOG_PADDING_BOTTOM })
197          .margin({ bottom: $r('sys.float.ohos_id_dialog_margin_bottom') })
198          .clip(true)
199        }.width(Constants.FULL_WIDTH)
200        .height(Constants.FULL_HEIGHT)
201      }
202    }.margin({ left: this.isBottomPopover ? Constants.DIALOG_MARGIN_VERTICAL : Constants.DIALOG_MARGIN,
203      right: this.isBottomPopover ? Constants.DIALOG_MARGIN_VERTICAL : Constants.DIALOG_MARGIN,
204      bottom: this.isBottomPopover ? this.naviHeight : 0})
205  }
206
207  showTitle(): ResourceStr {
208    let index = this.count >= this.grantGroups.length ? this.grantGroups.length - 1 : this.count;
209    if (this.grantGroups[index].name == 'LOCATION') {
210      if (this.locationFlag == Constants.LOCATION_FUZZY) {
211        return $r("app.string.access_general_location");
212      }
213      if (this.locationFlag == Constants.LOCATION_UPGRADE) {
214        return $r("app.string.fuzzy_to_exact");
215      }
216    }
217    return this.grantGroups[index].label;
218  }
219
220  showReason() {
221    let index = this.count >= this.grantGroups.length ? this.grantGroups.length - 1 : this.count;
222    if (this.grantGroups[index].name == 'LOCATION') {
223      if ((this.locationFlag == Constants.LOCATION_FUZZY) || (this.locationFlag == Constants.LOCATION_BOTH_FUZZY)) {
224        return true;
225      }
226    }
227    return false;
228  }
229
230  punctuation() {
231    let reason = this.grantGroups[this.count >= this.grantGroups.length ? this.grantGroups.length - 1 : this.count].reason;
232    return reason ? $r("app.string.comma") : $r("app.string.period");
233  }
234
235  verify() {
236    if ((this.initStatus == Constants.INIT_NEED_TO_TERMINATED) || (this.count >= this.grantGroups.length)) {
237      this.answerRequest();
238      this.initStatus = Constants.INIT_NEED_TO_WAIT;
239      return false;
240    }
241    return true;
242  }
243
244  answerRequest() {
245    let ret: number = Constants.RESULT_SUCCESS;
246    if (this.initStatus == Constants.INIT_NEED_TO_TERMINATED) {
247      ret = Constants.RESULT_FAILURE;
248    }
249    this.answer(ret, this.reqPerms);
250  }
251
252  answer(ret: number, reqPerms: string[]) {
253    Log.info("code:" + ret + ", perms="+ JSON.stringify(reqPerms) +", result=" + JSON.stringify(this.result));
254    let perms: string[] = [];
255    let results: number[] = [];
256    reqPerms.forEach(perm => {
257      perms.push(perm);
258    })
259    this.result.forEach(result => {
260      results.push(result);
261    })
262    let option = new rpc.MessageOption();
263    let data = new rpc.MessageSequence();
264    let reply = new rpc.MessageSequence();
265    Promise.all([data.writeInterfaceToken(Constants.ACCESS_TOKEN),
266      data.writeStringArray(perms),
267      data.writeIntArray(results)
268    ]).then(() => {
269      let proxy = want.parameters['ohos.ability.params.callback'].value as rpc.RemoteObject;
270      proxy.sendMessageRequest(Constants.RESULT_CODE, data, reply, option);
271      this.destruction();
272    }).catch(() => {
273      Log.error('write result failed!');
274      this.destruction();
275    })
276  }
277
278  destruction() {
279    let windowNum: number = GlobalContext.load('windowNum');
280    windowNum --;
281    Log.info("windowNum:" + windowNum);
282    GlobalContext.store('windowNum', windowNum);
283    win.destroyWindow();
284    if (windowNum == 0) {
285      this.context.terminateSelf();
286    }
287  }
288
289  async privacyAccept(group: GroupInfo, accessTokenId: number, permissionList: string[], userFixedFlag: number) {
290    let acManager = abilityAccessCtrl.createAtManager();
291    let num = 0;
292    group.permissions.forEach(async permission => {
293      let result: number = -1;
294      if (showSubpermissionsGrop.indexOf(group.name) == -1) {
295        if (group.name == 'LOCATION' && this.targetVersion >= Constants.API_VERSION_SUPPORT_STAGE) {
296          if (!(((this.locationFlag == Constants.LOCATION_BOTH_FUZZY) || (this.locationFlag == Constants.LOCATION_FUZZY))
297            && (permission == PRECISE_LOCATION_PERMISSION))) {
298            try {
299              await acManager.grantUserGrantedPermission(accessTokenId, permission, userFixedFlag).then(() => {
300                result = abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
301              })
302            }
303            catch(err) {
304              Log.error("failed to grant permission:" + permission);
305            }
306          }
307        } else {
308          try {
309            await acManager.grantUserGrantedPermission(accessTokenId, permission, userFixedFlag).then(() => {
310              result = abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
311            })
312          }
313          catch(err) {
314            Log.error("failed to grant permission:" + permission);
315          }
316        }
317      } else {
318        if (permissionList.includes(permission)) {
319          try {
320            await acManager.grantUserGrantedPermission(accessTokenId, permission, userFixedFlag).then(() => {
321              result = abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
322            })
323          }
324          catch(err) {
325            Log.error("failed to grant permission:" + permission);
326          }
327        }
328      }
329      num ++;
330      Log.info("grant permission permission" + permission);
331      if (result == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
332        permissionList.forEach((req, idx) => {
333          if (req == permission) {
334            this.result[idx] = abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
335          }
336        })
337        Log.info("grant permission success:" + permission);
338      } else {
339        Log.error("failed to grant permission:" + permission);
340      }
341      if (num == group.permissions.length) {
342        this.count ++;
343      }
344    })
345  }
346
347  async privacyCancel(group: GroupInfo, accessTokenId: number, permissionList: string[], userFixedFlag: number) {
348    let acManager = abilityAccessCtrl.createAtManager();
349    group.permissions.forEach(async permission => {
350      if (showSubpermissionsGrop.indexOf(group.name) == -1) {
351        if (!(this.locationFlag == Constants.LOCATION_UPGRADE && group.name == 'LOCATION') || permission == PRECISE_LOCATION_PERMISSION) {
352          try {
353            await acManager.revokeUserGrantedPermission(accessTokenId, permission, userFixedFlag);
354          }
355          catch(err) {
356            Log.error("failed to revoke permission:" + permission);
357          }
358        }
359      } else {
360        if (permissionList.includes(permission)) {
361          try {
362            await acManager.revokeUserGrantedPermission(accessTokenId, permission, userFixedFlag);
363          }
364          catch(err) {
365            Log.error("failed to revoke permission:" + permission);
366          }
367        }
368      }
369      Log.info("revoke permission " + permission);
370    })
371    this.count ++;
372  }
373
374  getgrantGroups(stateGroup: number[]) {
375    //Processing of positioning
376    if (this.targetVersion >= Constants.API_VERSION_SUPPORT_STAGE) {
377      if (this.reqPerms.includes(FUZZY_LOCATION_PERMISSION)) {
378        this.locationFlag = Constants.LOCATION_FUZZY;
379        if (this.reqPerms.includes(PRECISE_LOCATION_PERMISSION)) {
380          this.locationFlag = Constants.LOCATION_BOTH_PRECISE;
381          let fuzzyIndex = this.reqPerms.indexOf(FUZZY_LOCATION_PERMISSION);
382          if (stateGroup[fuzzyIndex] == Constants.PASS_OPER) {
383            this.locationFlag = Constants.LOCATION_UPGRADE;
384          }
385        }
386      }
387    }
388
389    this.reqPerms.forEach((permission, idx) => {
390      //已授权
391      if (stateGroup[idx] == Constants.PASS_OPER) {
392        Log.info("permission has been fixed:" + permission);
393        this.result[idx] = abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
394        //待授权
395      } else if (stateGroup[idx] == Constants.DYNAMIC_OPER) {
396        let group = getPermissionGroup(permission);
397        if (!userGrantPermissions.includes(permission)) {
398          Log.info("permission not find:" + permission);
399        } else {
400          let exist = this.grantGroups.find(grantGroup => grantGroup.name == group.name);
401          //判断是否为需要展示子权限的权限组
402          if (showSubpermissionsGrop.indexOf(group.name) != -1) {
403            let label = getPermissionLabel(permission)
404            if (!exist) {
405              group.description.push(label);
406              this.grantGroups.push(group);
407            } else {
408              if (exist.description.indexOf(label) == -1) {
409                exist.description.push($r("app.string.and"));
410                exist.description.push(label);
411              }
412            }
413          } else {
414            if (!exist) {
415              this.grantGroups.push(group);
416            }
417          }
418        }
419      }
420    })
421    this.initStatus = Constants.INIT_NEED_TO_VERIFY;
422  }
423
424  getApplicationName(bundleName: string) {
425    Log.info("getApplicationName bundleName:" + bundleName);
426    bundleManager.getApplicationInfo(bundleName, bundleManager.ApplicationFlag.GET_APPLICATION_INFO_DEFAULT).then(applicationInfo => {
427      let context = this.context.createBundleContext(bundleName);
428      context.resourceManager.getStringValue(applicationInfo.labelId, (err, value) => {
429        if (value == undefined) {
430          this.appName = titleTrim(applicationInfo.label);
431        } else {
432          this.appName = titleTrim(value);
433        }
434        Log.info("hap label:" + applicationInfo.label + ", value:"+this.appName);
435      })
436    }).catch((err: BusinessError) => {
437      Log.error("applicationInfo error :" + err);
438      this.initStatus = Constants.INIT_NEED_TO_TERMINATED;
439    })
440    this.grantGroups.forEach((group) => {
441      this.getReason(group, bundleName);
442    })
443  }
444
445  getReason(group: GroupInfo, bundleName: string) {
446    group.permissions.forEach(permission => {
447      if (this.reqPerms.indexOf(permission) != -1) {
448        this.reqPermissionDetails.forEach(reqPermissionDetail => {
449          if (reqPermissionDetail.name == permission) {
450            Log.info("reqPermissionDetail: " + JSON.stringify(reqPermissionDetail));
451            let context = this.context.createModuleContext(bundleName, reqPermissionDetail.moduleName);
452            context.resourceManager.getStringValue(reqPermissionDetail.reasonId, (err, value) => {
453              if (value !== undefined && group.reason === '') {
454                group.reason = value.slice(Constants.START_SUBSCRIPT, Constants.END_SUBSCRIPT);
455                this.refresh ++;
456              }
457              this.initStatus = Constants.INIT_NEED_TO_REFRESH;
458            })
459          }
460        })
461      }
462    })
463  }
464
465  getAvoidWindow() {
466    let type = window.AvoidAreaType.TYPE_SYSTEM;
467    try {
468      win.on('avoidAreaChange', (data) => {
469        if (data.type == window.AvoidAreaType.TYPE_SYSTEM) {
470          Log.info('avoidAreaChange: ' + JSON.stringify(data));
471          this.naviHeight = data.area.bottomRect.height;
472        }
473      });
474      let avoidArea = win.getWindowAvoidArea(type);
475      Log.info('avoidArea: ' + JSON.stringify(avoidArea));
476      this.naviHeight = avoidArea.bottomRect.height;
477    } catch (exception) {
478      Log.error('Failed to obtain the area. Cause:' + JSON.stringify(exception));
479    }
480  }
481
482  getPopupPosition() {
483    try {
484      let dis = display.getDefaultDisplaySync();
485      let isVertical = dis.width > dis.height ? false : true;
486      this.isBottomPopover = (bottomPopoverTypes.includes(deviceInfo.deviceType) && isVertical) ? true : false;
487    } catch (exception) {
488      Log.error('Failed to obtain the default display object. Code: ' + JSON.stringify(exception));
489    };
490  }
491
492  updateReason() {
493    if (this.isUpdate > 0) {
494      this.getApplicationName(want.parameters['ohos.aafwk.param.callerBundleName'])
495    }
496  }
497
498  aboutToAppear() {
499    this.count = 0;
500    this.initStatus = Constants.INIT_NEED_TO_WAIT;
501    this.result = [];
502    this.reqPerms = want.parameters['ohos.user.grant.permission'];
503    this.accessTokenId = want.parameters['ohos.aafwk.param.callerToken'];
504    if (this.reqPerms == undefined || this.accessTokenId == undefined || this.reqPerms.length == 0) {
505      Log.info("invalid parameters");
506      this.initStatus = Constants.INIT_NEED_TO_TERMINATED;
507      return;
508    }
509    Log.info("request permission=" + JSON.stringify(this.reqPerms) + ", tokenId = " + this.accessTokenId);
510    Log.info("permission state=" + JSON.stringify(want.parameters['ohos.user.grant.permission.state']));
511    this.result = new Array(this.reqPerms.length).fill(-1);
512    this.getAvoidWindow();
513    this.getPopupPosition();
514    let bundleName: string = want.parameters['ohos.aafwk.param.callerBundleName'];
515    bundleManager.getBundleInfo(bundleName, bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION).then(bundleInfo => {
516      this.targetVersion = bundleInfo.targetVersion;
517      this.reqPermissionDetails = bundleInfo.reqPermissionDetails;
518      this.getgrantGroups(want.parameters['ohos.user.grant.permission.state']);
519      this.getApplicationName(bundleName);
520    }).catch((err: BusinessError) => {
521      Log.error("getBundleInfo error :" + JSON.stringify(err));
522      this.initStatus = Constants.INIT_NEED_TO_TERMINATED;
523    })
524  }
525}
526
527