1/* 2 * Copyright (c) 2025 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 deviceManager from '@ohos.distributedHardware.deviceManager'; 16import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession'; 17import deviceInfo from '@ohos.deviceInfo'; 18import Constant from '../common/constant'; 19import common from '@ohos.app.ability.common'; 20import display from '@ohos.display'; 21import i18n from '@ohos.i18n'; 22import { KeyCode } from '@ohos.multimodalInput.keyCode'; 23 24let dmClass: deviceManager.DeviceManager | null; 25let TAG = '[DeviceManagerUI:ConfirmDialog]==>'; 26const ACTION_ALLOW_AUTH_ONCE: number = 0; 27const ACTION_CANCEL_AUTH: number = 1; 28const ACTION_AUTH_CONFIRM_TIMEOUT: number = 2; 29const ACTION_ALLOW_AUTH_ALWAYS: number = 6; 30const MSG_CANCEL_CONFIRM_SHOW: number = 5; 31const DEVICE_TYPE_2IN1: number = 0xA2F; 32const DEVICE_TYPE_PC: number = 0x0C; 33const CAST_PKG_NAME: string = 'CastEngineService'; 34 35@CustomDialog 36struct ConfirmCustomDialog { 37 @State peerAppOperation: string = ''; 38 @State peerCustomDescription: string = ''; 39 @State peerDeviceName: string = ''; 40 @State peerDeviceType: number = 0; 41 @State secondsNum: number = 30; 42 @State times: number = 0; 43 @State firstButtonWidth: number = 1; 44 @State firstButtonHeight: number = 1; 45 @State secondButtonWidth: number = 1; 46 @State secondButtonHeight: number = 1; 47 @State thirdButtonWidth: number = 1; 48 @State thirdButtonHeight: number = 1; 49 @State isAvailableType: boolean = false; 50 @State btnColor: ResourceColor = Color.Transparent; 51 @State title: string = ''; 52 @State mLocalWidth: number = 0; 53 controller?: CustomDialogController; 54 isPC: boolean = false; 55 aboutToAppear() { 56 console.log(TAG + 'aboutToAppear execute PinCustomDialog') 57 console.log(this.mLocalWidth + 'mLocalWidth') 58 try { 59 this.mLocalWidth = display.getDefaultDisplaySync().width; 60 } catch (err) { 61 console.error('Failed to get display width:', err); 62 this.mLocalWidth = 0; 63 } 64 let context = getContext() as common.UIAbilityContext; 65 66 if (AppStorage.get('deviceName') != null) { 67 this.peerDeviceName = AppStorage.get('deviceName') as string + ' '; 68 } 69 let customDescriptionStr: string = AppStorage.get('customDescriptionStr') as string; 70 let hostPkgLabel: string = AppStorage.get('hostPkgLabel') as string; 71 if (hostPkgLabel === CAST_PKG_NAME) { 72 this.title = 73 context.resourceManager.getStringSync($r('app.string.dm_confirm_title_cast').id, this.peerDeviceName); 74 } else if (hostPkgLabel != null) { 75 this.title = context.resourceManager.getStringSync($r('app.string.dm_confirm_title_hap').id, hostPkgLabel, 76 this.peerDeviceName); 77 this.peerCustomDescription = context.resourceManager.getStringSync($r('app.string.dm_confirm_intention').id); 78 if (customDescriptionStr != undefined && customDescriptionStr != '') { 79 this.peerCustomDescription = this.peerDeviceName + customDescriptionStr; 80 } 81 } else { 82 let titleFirst: string = 83 context.resourceManager.getStringSync($r('app.string.dm_connect_device').id, this.peerDeviceName); 84 this.title = 85 context.resourceManager.getStringSync($r('app.string.dm_is_trust_device').id, titleFirst); 86 this.peerCustomDescription = context.resourceManager.getStringSync($r('app.string.dm_confirm_intention').id); 87 } 88 89 if (AppStorage.get('deviceType') != null) { 90 this.peerDeviceType = AppStorage.get('deviceType') as number; 91 console.log('peerDeviceType is ' + this.peerDeviceType); 92 } 93 94 this.times = setInterval(() => { 95 console.info('devicemanagerui confirm dialog run seconds:' + this.secondsNum); 96 this.secondsNum--; 97 if (this.secondsNum === 0) { 98 clearInterval(this.times); 99 this.times = 0; 100 this.setUserOperation(ACTION_AUTH_CONFIRM_TIMEOUT); 101 this.destruction(); 102 console.info('click cancel times run out'); 103 } 104 }, 1000) 105 console.log(TAG + 'deviceInfo.deviceType:' + deviceInfo.deviceType); 106 this.isPC = Constant.isPC(); 107 } 108 109 onAllowOnce() { 110 console.log('allow once') 111 if (dmClass == null) { 112 console.log('createDeviceManager is null') 113 return 114 } 115 116 console.log('allow once' + ACTION_ALLOW_AUTH_ONCE) 117 this.setUserOperation(ACTION_ALLOW_AUTH_ONCE) 118 this.destruction() 119 } 120 121 onAllowAlways() { 122 console.log('allow always') 123 if (dmClass == null) { 124 console.log('createDeviceManager is null') 125 return 126 } 127 128 console.log('allow always' + ACTION_ALLOW_AUTH_ALWAYS) 129 this.setUserOperation(ACTION_ALLOW_AUTH_ALWAYS) 130 this.destruction() 131 } 132 133 onCancel() { 134 console.log('cancel') 135 if (dmClass == null) { 136 console.log('createDeviceManager is null') 137 return 138 } 139 140 console.log('cancel' + ACTION_CANCEL_AUTH) 141 this.setUserOperation(ACTION_CANCEL_AUTH) 142 this.destruction() 143 } 144 145 setUserOperation(operation: number) { 146 console.log(TAG + 'setUserOperation: ' + operation) 147 if (dmClass == null) { 148 console.log(TAG + 'setUserOperation: ' + 'dmClass null') 149 return; 150 } 151 try { 152 dmClass.setUserOperation(operation, 'extra'); 153 } catch (error) { 154 console.log(TAG + 'dmClass setUserOperation failed') 155 } 156 } 157 158 destruction() { 159 let session = AppStorage.get<UIExtensionContentSession>('ConfirmSession'); 160 if (session) { 161 session.terminateSelf(); 162 } 163 } 164 165 getImages(peerdeviceType: number): Resource { 166 console.info('peerdeviceType is ' + peerdeviceType); 167 if (peerdeviceType === deviceManager.DeviceType.SPEAKER) { 168 this.isAvailableType = true; 169 return $r('sys.symbol.soundai_fill'); 170 } else if (peerdeviceType === deviceManager.DeviceType.PHONE) { 171 this.isAvailableType = true; 172 return $r('sys.symbol.phone_fill_1'); 173 } else if (peerdeviceType === deviceManager.DeviceType.TABLET) { 174 this.isAvailableType = true; 175 return $r('sys.symbol.pad_fill'); 176 } else if (peerdeviceType === deviceManager.DeviceType.WEARABLE) { 177 this.isAvailableType = true; 178 return $r('sys.symbol.earphone_case_16896'); 179 } else if (peerdeviceType === deviceManager.DeviceType.CAR) { 180 this.isAvailableType = true; 181 return $r('sys.symbol.car_fill'); 182 } else if (peerdeviceType === deviceManager.DeviceType.TV) { 183 this.isAvailableType = true; 184 return $r('sys.symbol.smartscreen_fill'); 185 } else if (peerdeviceType === DEVICE_TYPE_PC) { 186 this.isAvailableType = true; 187 return $r('sys.symbol.matebook_fill'); 188 } else if (peerdeviceType === DEVICE_TYPE_2IN1) { 189 this.isAvailableType = true; 190 return $r('sys.symbol.matebook_fill'); 191 } else { 192 this.isAvailableType = false; 193 return $r('sys.symbol.unknown_device_fill'); 194 } 195 } 196 197 @Builder 198 Symbol() { 199 Shape() { 200 Column() { 201 SymbolGlyph(this.getImages(this.peerDeviceType)) 202 .fontSize('70vp') 203 .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_OPACITY) 204 .fontColor(['#E5FFFFFF']) 205 } 206 } 207 .visibility(this.isAvailableType ? Visibility.Visible : Visibility.None) 208 .margin({ bottom: '24vp', top: '24vp' }) 209 } 210 211 private isTibetanLanguages(): boolean { 212 console.info(`${TAG} isTibetanLanguages in`); 213 let locale = new Intl.Locale(i18n.System.getSystemLanguage()).toString(); 214 console.info(`${TAG} isTibetanLanguages: ${locale}`); 215 return Constant.TIBETAN_LANGUAGES.includes(locale); 216 } 217 218 build() { 219 GridRow({ 220 columns: { xs: 4, sm: 8, md: 24 }, 221 gutter: { x: 4 }, 222 breakpoints: { value: ['600vp', '840vp'] } 223 }) { 224 GridCol({ span: { xs: 4, sm: 4, md: 6 }, offset: { sm: 2, md: 9 } }) { 225 Column() { 226 this.Symbol(); 227 Column() { 228 Text(this.title) 229 .textAlign(TextAlign.Center) 230 .fontSize('16fp') 231 .fontWeight(FontWeight.Regular) 232 .fontColor('#E5FFFFFF') 233 .heightAdaptivePolicy(TextHeightAdaptivePolicy.LAYOUT_CONSTRAINT_FIRST) 234 .lineHeight(this.isTibetanLanguages() ? '22vp' : 0) 235 .textOverflow({ overflow: TextOverflow.Ellipsis }) 236 .width('auto') 237 .maxLines(2) 238 Text(this.peerCustomDescription) 239 .textAlign(TextAlign.Start) 240 .fontColor('#E5FFFFFF') 241 .fontWeight(FontWeight.Regular) 242 .textOverflow({ overflow: TextOverflow.Ellipsis }) 243 .fontSize('16vp') 244 .maxLines(2) 245 .width('auto') 246 .lineHeight(this.isTibetanLanguages() ? '22vp' : 0) 247 .margin({ top: '8vp' }) 248 .visibility(this.peerCustomDescription === '' ? Visibility.None : Visibility.Visible) 249 }.margin({ 250 top: this.isAvailableType ? 0 : '24vp', 251 bottom: '24vp', left: '24vp', right: '24vp' }) 252 253 Column() { 254 Button($r('app.string.dm_allow_always')) 255 .margin({ bottom: '12vp' }) 256 .onClick(() => { 257 this.onAllowAlways(); 258 }) 259 .fontSize('18vp') 260 .fontColor('#E5FFFFFF') 261 .fontWeight(FontWeight.Medium) 262 .height(this.isTibetanLanguages() ? 'auto' : '40vp') 263 .width('100%') 264 .backgroundColor(this.btnColor) 265 .scale({x: this.firstButtonWidth, y: this.firstButtonHeight}) 266 .focusable(true) 267 .border({ 268 width: '0.5vp', 269 color: '#26ffffff' 270 }) 271 .onHover((isHover?: boolean, event?: HoverEvent): void => { 272 if (isHover) { 273 this.btnColor = '#4DFFFFFF'; 274 this.firstButtonWidth = 1.05; 275 this.firstButtonHeight = 1.05; 276 } else { 277 this.btnColor = '#33F1F3F5'; 278 this.firstButtonWidth = 1; 279 this.firstButtonHeight = 1; 280 } 281 }) 282 .stateStyles({ 283 pressed: { 284 .backgroundColor('#26FFFFFF') 285 }, 286 focused: { 287 .scale({x: 1.05, y: 1.05}) 288 .backgroundColor('#4DFFFFFF') 289 }, 290 normal: { 291 .backgroundColor('#33F1F3F5') 292 } 293 }) 294 Button($r('app.string.dm_allow_this_time')) 295 .margin({ bottom: '12vp' }) 296 .onClick(() => { 297 this.onAllowOnce(); 298 }) 299 .fontSize('18vp') 300 .fontColor('#E5FFFFFF') 301 .fontWeight(FontWeight.Medium) 302 .focusable(true) 303 .height(this.isTibetanLanguages() ? 'auto' : '40vp') 304 .width('100%') 305 .backgroundColor(this.btnColor) 306 .scale({x: this.secondButtonWidth, y: this.secondButtonHeight}) 307 .border({ 308 width: '0.5vp', 309 color: '#26ffffff' 310 }) 311 .onHover((isHover?: boolean, event?: HoverEvent): void => { 312 if (isHover) { 313 this.btnColor = '#4DFFFFFF'; 314 this.secondButtonWidth = 1.05; 315 this.secondButtonHeight = 1.05; 316 } else { 317 this.btnColor = '#33F1F3F5'; 318 this.secondButtonWidth = 1; 319 this.secondButtonHeight = 1; 320 } 321 }) 322 .stateStyles({ 323 pressed: { 324 .backgroundColor('#26FFFFFF') 325 }, 326 focused: { 327 .scale({x: 1.05, y: 1.05}) 328 .backgroundColor('#4DFFFFFF') 329 }, 330 normal: { 331 .backgroundColor('#33F1F3F5') 332 } 333 }) 334 Button($r('app.plural.dm_not_allow', this.secondsNum, this.secondsNum)) 335 .margin({ left: '16vp', right: '16vp' }) 336 .fontSize('18vp') 337 .fontColor('#E5FFFFFF') 338 .fontWeight(FontWeight.Medium) 339 .focusable(true) 340 .onKeyEvent((event?: KeyEvent) => { 341 if (event && event?.keyCode === KeyCode.KEYCODE_HOME && event?.type === KeyType.Down) { 342 console.log(TAG + 'onKeyEvent KEYCODE_HOME:' + event?.type) 343 return; 344 } 345 if (event?.keyCode == KeyCode.KEYCODE_HOME) { 346 console.log(TAG + 'onKeyEvent KEYCODE_HOME:' + event?.type) 347 this.onCancel(); 348 } 349 }) 350 .onClick(() => { 351 this.onCancel(); 352 }) 353 .height(this.isTibetanLanguages() ? 'auto' : '40vp') 354 .width('100%') 355 .backgroundColor(this.btnColor) 356 .scale({x: this.thirdButtonWidth, y: this.thirdButtonHeight}) 357 .border({ 358 width: '0.5vp', 359 color: '#26ffffff' 360 }) 361 .onHover((isHover?: boolean, event?: HoverEvent): void => { 362 if (isHover) { 363 this.btnColor = '#4DFFFFFF'; 364 this.thirdButtonWidth = 1.05; 365 this.thirdButtonHeight = 1.05; 366 } else { 367 this.btnColor = '#33F1F3F5'; 368 this.thirdButtonWidth = 1; 369 this.thirdButtonHeight = 1; 370 } 371 }) 372 .stateStyles({ 373 pressed: { 374 .backgroundColor('#26FFFFFF') 375 }, 376 focused: { 377 .scale({x: 1.05, y: 1.05}) 378 .backgroundColor('#4DFFFFFF') 379 }, 380 normal: { 381 .backgroundColor('#33F1F3F5') 382 } 383 }) 384 } 385 .margin({ 386 left: '24vp', 387 right: '24vp', 388 bottom: '24vp' 389 }) 390 } 391 .backgroundColor('#3C3C3C') 392 .borderRadius(16) 393 .border({ 394 width: '0.5', 395 color: '#26ffffff', 396 radius: '16vp' 397 }) 398 .margin({ left: $r('sys.float.ohos_id_dialog_margin_start'), right: $r('sys.float.ohos_id_dialog_margin_end') }) 399 .width(px2vp(this.mLocalWidth) * 0.36) 400 } 401 .onKeyPreIme((event:KeyEvent) => { 402 if (event?.keyCode === KeyCode.KEYCODE_MENU && event?.type === KeyType.Up) { 403 console.log(TAG + 'onKeyEvent KEYCODE_MENU:' + event?.type) 404 return true; 405 } 406 return false; 407 }) 408 .onKeyEvent((event?: KeyEvent) => { 409 if (event?.keyCode == KeyCode.KEYCODE_HOME) { 410 console.log(TAG + 'onKeyEvent KEYCODE_HOME:' + event?.type) 411 this.onCancel(); 412 } 413 }) 414 } 415 .constraintSize({ maxHeight: '80%' }) 416 } 417} 418 419@Entry 420@Component 421struct dialogPlusPage { 422 dialogController: CustomDialogController = new CustomDialogController({ 423 builder: ConfirmCustomDialog(), 424 autoCancel: false, 425 onWillDismiss: ()=>{ 426 this.onWillDismiss() 427 }, 428 alignment: DialogAlignment.Center, 429 customStyle: true, 430 maskColor: ('#CC000000') 431 }); 432 433 initStatue() { 434 if (dmClass) { 435 console.log(TAG + 'deviceManager exist') 436 return 437 } 438 deviceManager.createDeviceManager('com.ohos.devicemanagerui.confirm', 439 (err: Error, dm: deviceManager.DeviceManager) => { 440 if (err) { 441 console.log('createDeviceManager err:' + JSON.stringify(err) + ' --fail:' + JSON.stringify(dm)) 442 return 443 } 444 dmClass = dm 445 dmClass.on('uiStateChange', (data: Record<string, string>) => { 446 console.log('uiStateChange executed, dialog closed' + JSON.stringify(data)) 447 let tmpStr: Record<string, number> = JSON.parse(data.param) 448 let msg: number = tmpStr.uiStateMsg as number 449 if (msg === MSG_CANCEL_CONFIRM_SHOW) { 450 console.log('cancel confirm show.') 451 this.destruction() 452 return 453 } 454 }) 455 }) 456 } 457 458 onWillDismiss() { 459 console.log(TAG + 'onWillDismiss: ' + ACTION_CANCEL_AUTH) 460 this.setUserOperation(ACTION_CANCEL_AUTH); 461 this.destruction(); 462 } 463 464 setUserOperation(operation: number) { 465 console.log(TAG + 'setUserOperation: ' + operation) 466 if (dmClass == null) { 467 console.log(TAG + 'setUserOperation: ' + 'dmClass null') 468 return; 469 } 470 try { 471 dmClass.setUserOperation(operation, 'extra'); 472 } catch (error) { 473 console.log(TAG + 'dmClass setUserOperation failed') 474 } 475 } 476 477 onPageShow() { 478 console.log('onPageShow') 479 this.initStatue() 480 } 481 482 destruction() { 483 let session = AppStorage.get<UIExtensionContentSession>('ConfirmSession'); 484 if (session) { 485 session.terminateSelf(); 486 } 487 } 488 489 aboutToDisappear() { 490 console.log(TAG + 'aboutToDisappear aboutToDisappear') 491 if (dmClass != null) { 492 try { 493 dmClass.off('uiStateChange'); 494 dmClass.release(); 495 } catch (error) { 496 console.log('dmClass release failed') 497 } 498 dmClass = null 499 } 500 } 501 502 build() { 503 Column(this.dialogController.open()) 504 } 505}