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