• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}