1/**
2 * Copyright (c) 2024 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 hiLog from '@ohos.hilog';
17import userAuth from '@ohos.userIAM.userAuth';
18
19const DOMAIN: number = 0x0007;
20const TAG: string = 'useriam_auth_icon';
21const ICON_UNAVAILABLE: number = 0;
22const ICON_AVAILABLE: number = 1;
23const TIMEOUT_MILLISECONDS: number = 5000;
24const INVALID_PARAMETERS: number = 401;
25const FACE_ICON_RESOURCE: string = 'sys.symbol.face';
26const FINGERPRINT_ICON_RESOURCE: string = 'sys.symbol.touchid';
27
28/**
29 * Declare UserAuthIcon, use for user authentication.
30 *
31 * @syscap SystemCapability.UserIAM.UserAuth.Core
32 * @since 12
33 */
34@Component
35export struct UserAuthIcon {
36  /**
37   * Set user auth parameter.
38   *
39   * @type { userAuth.AuthParam }
40   * @syscap SystemCapability.UserIAM.UserAuth.Core
41   * @since 12
42   */
43  authParam: userAuth.AuthParam = {
44    challenge: new Uint8Array(),
45    authType: [],
46    authTrustLevel: userAuth.AuthTrustLevel.ATL1
47  };
48  /**
49   * Set auth widget parameter.
50   *
51   * @type { userAuth.WidgetParam }
52   * @syscap SystemCapability.UserIAM.UserAuth.Core
53   * @since 12
54   */
55  widgetParam: userAuth.WidgetParam = {
56    title: ''
57  };
58  /**
59   * Set the height of the icon, width and height ratio 1:1.
60   *
61   * @type { ?Dimension }
62   * @default 64
63   * @syscap SystemCapability.UserIAM.UserAuth.Core
64   * @since 12
65   */
66  iconHeight?: Dimension = 64;
67  /**
68   * Set the color of the icon.
69   *
70   * @type { ?ResourceColor }
71   * @default $r('sys.color.ohos_id_color_activated')
72   * @syscap SystemCapability.UserIAM.UserAuth.Core
73   * @since 12
74   */
75  iconColor?: ResourceColor = $r('sys.color.ohos_id_color_activated');
76  authFlag: number = ICON_UNAVAILABLE;
77  @State imageSource: string = '';
78  /**
79   * The authentication result code is returned through the callback.
80   * If the authentication is passed, the authentication token and auth type will be returned.
81   * You need to apply for permission:ohos.permission.ACCESS_BIOMETRIC to use user authentication ability.
82   *
83   * @param { userAuth.UserAuthResult } result - Authentication result information.
84   * @syscap SystemCapability.UserIAM.UserAuth.Core
85   * @since 12
86   */
87  onAuthResult: (result: userAuth.UserAuthResult) => void = (result: userAuth.UserAuthResult) => {
88  };
89  /**
90   * When user click the icon, it will be returned through this callback.
91   *
92   * @syscap SystemCapability.UserIAM.UserAuth.Core
93   * @since 12
94   */
95  onIconClick: () => void = () => {
96  };
97
98  private initImageSource(authTypes: userAuth.UserAuthType[], authTrustLevel: userAuth.AuthTrustLevel): void {
99    if (authTypes.includes(userAuth.UserAuthType.FACE) && (!authTypes.includes(userAuth.UserAuthType.FINGERPRINT))) {
100      // Handle the situation where the authTypes parameter contains face type but not contains fingerprint type.
101      this.authFlag = ICON_AVAILABLE;
102      this.imageSource = FACE_ICON_RESOURCE;
103      return;
104    }
105    if ((!authTypes.includes(userAuth.UserAuthType.FACE)) && authTypes.includes(userAuth.UserAuthType.FINGERPRINT)) {
106      // Handle the situation where the authTypes parameter contains fingerprint type but not contains face type.
107      this.authFlag = ICON_AVAILABLE;
108      this.imageSource = FINGERPRINT_ICON_RESOURCE;
109      return;
110    }
111    if (authTypes.includes(userAuth.UserAuthType.FACE) && authTypes.includes(userAuth.UserAuthType.FINGERPRINT) &&
112      authTypes.includes(userAuth.UserAuthType.PIN)) {
113      // Handle the situation where the authTypes parameter contains face, fingerprint, and PIN types at the same time.
114      this.handleAllAuthTypeCase(authTrustLevel);
115      return;
116    }
117    if (authTypes.includes(userAuth.UserAuthType.FACE) && authTypes.includes(userAuth.UserAuthType.FINGERPRINT) &&
118      !authTypes.includes(userAuth.UserAuthType.PIN)) {
119      // Handle the situation where the authTypes parameter contains face, fingerprint, but not contains PIN types at
120      // the same time.
121      this.authFlag = ICON_UNAVAILABLE;
122      this.info('incorrect parameters.');
123      this.onAuthResult({ result: INVALID_PARAMETERS });
124      this.imageSource = '';
125      return;
126    }
127    // Default processing, includes the situation where the authTypes parameter only contains face and fingerprint
128    // types or the situation where the authTypes parameter only contains PIN type.
129    this.authFlag = ICON_UNAVAILABLE;
130    this.info('incorrect parameters.');
131    this.onAuthResult({ result: userAuth.UserAuthResultCode.TYPE_NOT_SUPPORT });
132    this.imageSource = '';
133    return;
134  }
135
136  private handleAllAuthTypeCase(authTrustLevel: userAuth.AuthTrustLevel): void {
137    if (this.checkAuthTypeSupported(userAuth.UserAuthType.FACE, authTrustLevel)) {
138      this.info('face auth available.');
139      this.authFlag = ICON_AVAILABLE;
140      this.imageSource = FACE_ICON_RESOURCE;
141      return;
142    }
143    if (this.checkAuthTypeSupported(userAuth.UserAuthType.FINGERPRINT, authTrustLevel)) {
144      this.info('finger auth available.');
145      this.authFlag = ICON_AVAILABLE;
146      this.imageSource = FINGERPRINT_ICON_RESOURCE;
147      return;
148    }
149    this.authFlag = ICON_AVAILABLE;
150    this.imageSource = FACE_ICON_RESOURCE;
151    return;
152  }
153
154  private checkAuthTypeSupported(authType: userAuth.UserAuthType, authTrustLevel: userAuth.AuthTrustLevel): boolean {
155    this.info(`check if it is supported, authType: ${authType} authTrustLevel: ${authTrustLevel}.`);
156    try {
157      userAuth.getAvailableStatus(authType, authTrustLevel);
158      this.info('current auth trust level is supported.');
159      return true;
160    } catch (error) {
161      this.error(`current auth trust level is not supported, error = ${error}.`);
162      return false;
163    }
164  }
165
166  private info(format: string): void {
167    if (hiLog.isLoggable(DOMAIN, TAG, hiLog.LogLevel.INFO)) {
168      hiLog.info(DOMAIN, TAG, format);
169    }
170  }
171
172  private error(format: string): void {
173    if (hiLog.isLoggable(DOMAIN, TAG, hiLog.LogLevel.ERROR)) {
174      hiLog.error(DOMAIN, TAG, format);
175    }
176  }
177
178  aboutToAppear(): void {
179    this.info('before init image source.');
180    if (this.authParam.authType === undefined || this.authParam.authTrustLevel === undefined) {
181      this.authFlag = ICON_UNAVAILABLE;
182      this.info('incorrect parameters.');
183      this.onAuthResult({ result: INVALID_PARAMETERS });
184      this.imageSource = '';
185      return;
186    }
187    this.initImageSource(this.authParam.authType, this.authParam.authTrustLevel);
188    this.info(`after init image source, imageSource = ${this.imageSource}.`);
189  }
190
191  build() {
192    Row() {
193      Column() {
194        SymbolGlyph($r(this.imageSource))
195          .fontSize(this.iconHeight)
196          .fontColor([this.iconColor])
197          .onClick(() => {
198            this.info('start handle click event.');
199            if (this.onIconClick !== undefined) {
200              this.info('click event has response.');
201              this.onIconClick();
202            }
203            if (this.authFlag === ICON_AVAILABLE) {
204              try {
205                let userAuthInstance: userAuth.UserAuthInstance =
206                  userAuth.getUserAuthInstance(this.authParam, this.widgetParam);
207                let timer: number = setTimeout(() => {
208                  this.error('auth timeout.');
209                  userAuthInstance.cancel();
210                  this.onAuthResult({ result: userAuth.UserAuthResultCode.GENERAL_ERROR });
211                }, TIMEOUT_MILLISECONDS)
212                this.info('get userAuth instance success.');
213                userAuthInstance.on('result', {
214                  onResult: (result) => {
215                    this.info(`userAuthInstance callback result = ${JSON.stringify(result)}.`);
216                    this.onAuthResult(result);
217                    userAuthInstance.off('result');
218                  }
219                });
220                this.info('auth before start.');
221                userAuthInstance.start();
222                this.info('auth start success.');
223                clearTimeout(timer);
224              } catch (error) {
225                if (error) {
226                  this.error(`auth catch error, code: ${error.code}, message: ${error.message}`);
227                  this.onAuthResult({ result: error.code });
228                  return;
229                }
230                this.error('auth error.');
231                this.onAuthResult({ result: userAuth.UserAuthResultCode.GENERAL_ERROR });
232              }
233            }
234            this.info('end handle click event.');
235          })
236      }
237    }
238  }
239}
240