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