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 web_webview from '@ohos.web.webview';
17import router from '@ohos.router';
18import deviceInfo from '@ohos.deviceInfo';
19import common from '@ohos.app.ability.common';
20import geoLocationManager from '@ohos.geoLocationManager';
21import bundleManager from '@ohos.bundle.bundleManager';
22import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
23import connection from '@ohos.net.connection';
24import request from '@ohos.request';
25import fs from '@ohos.file.fs';
26import util from '@ohos.util';
27import photoAccessHelper from '@ohos.file.photoAccessHelper';
28import { filePreview } from '@kit.PreviewKit';
29import fileUri from '@ohos.file.fileuri';
30import picker from '@ohos.multimedia.cameraPicker';
31import filePicker from '@ohos.file.picker';
32import { BusinessError } from '@ohos.base';
33
34class AsError {
35  public code: number;
36  public message: string;
37
38  constructor(code: number, message: string) {
39    this.code = code;
40    this.message = message;
41  }
42}
43
44class JsApiConfig {
45  public apiName: string;
46  public minVersion: string;
47  public maxVersion: string;
48  public requiredFieldNames?: string[];
49
50  constructor(apiName: string, minVersion: string, maxVersion: string, requiredFieldNames?: string[]) {
51    this.apiName = apiName;
52    this.minVersion = minVersion;
53    this.maxVersion = maxVersion;
54    this.requiredFieldNames = requiredFieldNames;
55  }
56}
57
58const LOG_ENABLE: boolean = true;
59const LOG_PREFIX: string = '[AtomicServiceWebLog]';
60const UPLOAD_IMAGE_CACHE_DIR: string = '/cache/';
61const JAVA_SCRIPT_PROXY_OBJECT_NAME: string = 'atomicServiceProxy';
62const JAVA_SCRIPT_PROXY_API_NAME_LIST: string[] = ['invokeJsApi'];
63const ATOMIC_SERVICE_JS_API_MAP = new Map<string, JsApiConfig>();
64const registerJsApi = (apiNameAlias: string, apiName: string, minVersion: string, maxVersion: string,
65  requiredFieldNames: string[]): void => {
66  ATOMIC_SERVICE_JS_API_MAP.set(apiNameAlias, new JsApiConfig(apiName, minVersion, maxVersion, requiredFieldNames));
67};
68const MAX_VERSION = '99.99.99';
69const ATOMIC_SERVICE_JS_SDK_CURRENT_VERSION = '1.0.0';
70const PERMISSION_APPROXIMATELY_LOCATION: Permissions = 'ohos.permission.APPROXIMATELY_LOCATION';
71
72const SYSTEM_INTERNAL_ERROR: AsError = new AsError(500, 'System internal error.');
73const JS_API_INVALID_INVOKE_ERROR: AsError = new AsError(200001, 'Invalid invoke.');
74const PARAM_REQUIRED_ERROR_CODE: number = 200002;
75const PARAM_NUMBER_POSITIVE_ERROR_CODE: number = 200003;
76const ROUTER_PARAM_MODE_INVALID_ERROR: AsError = new AsError(200004, 'Param mode is invalid.');
77const BACK_URL_NOT_EXIST_OR_OPENED_ERROR: AsError = new AsError(200005, 'Url is not exist or opened, can not be back.');
78const NAV_PATH_STACK_NOT_EXIST_ERROR_CODE: number = 200006;
79const POP_PATH_NAME_NOT_EXIST_ERROR: AsError = new AsError(200007, 'Name is not exist or opened, can not be pop.');
80const POP_PATH_PARAM_INDEX_INVALID_ERROR: AsError = new AsError(200008, 'Param index is invalid.');
81const POP_PATH_INDEX_OUT_OF_RANGE_ERROR: AsError = new AsError(200009, 'The Index is out of range.');
82const UPLOAD_IMAGE_FILES_REQUIRED_ERROR: AsError = new AsError(200010, 'Param files is required.');
83const UPLOAD_IMAGE_FILE_NOT_EXIST_ERROR_CODE: number = 200011;
84const UPLOAD_IMAGE_FILES_URI_REQUIRED_ERROR: AsError = new AsError(200012, 'Param uri of files is required.');
85const UPLOAD_FILE_ERROR: AsError = new AsError(200013, 'Upload file error.');
86const IMAGE_CAN_NOT_PREVIEW_ERROR: AsError = new AsError(200014, 'The filePath can not preview.');
87const NETWORK_NO_ACTIVE_ERROR: AsError = new AsError(200015, 'The network is not active.');
88const PERMISSION_LOCATION_USER_REFUSED_ERROR: number = 200016;
89
90registerJsApi('router.pushUrl', 'pushUrl', '1.0.0', MAX_VERSION, ['url']);
91registerJsApi('router.replaceUrl', 'replaceUrl', '1.0.0', MAX_VERSION, ['url']);
92registerJsApi('router.back', 'backUrl', '1.0.0', MAX_VERSION, []);
93registerJsApi('router.clear', 'clearUrl', '1.0.0', MAX_VERSION, []);
94registerJsApi('navPathStack.pushPath', 'pushPath', '1.0.0', MAX_VERSION, ['name']);
95registerJsApi('navPathStack.replacePath', 'replacePath', '1.0.0', MAX_VERSION, ['name']);
96registerJsApi('navPathStack.pop', 'popPath', '1.0.0', MAX_VERSION, []);
97registerJsApi('navPathStack.clear', 'clearPath', '1.0.0', MAX_VERSION, []);
98registerJsApi('asWeb.postMessage', 'postMessage', '1.0.0', MAX_VERSION, ['data']);
99registerJsApi('asWeb.getEnv', 'getEnv', '1.0.0', MAX_VERSION, []);
100registerJsApi('asWeb.checkJsApi', 'checkJsApi', '1.0.0', MAX_VERSION, ['jsApiList']);
101registerJsApi('cameraPicker.pick', 'pickCamera', '1.0.0', MAX_VERSION, ['mediaTypes', 'cameraPosition']);
102registerJsApi('photoViewPicker.select', 'selectPhoto', '1.0.0', MAX_VERSION, []);
103registerJsApi('filePreview.openPreview', 'openPreview', '1.0.0', MAX_VERSION, ['uri']);
104registerJsApi('request.uploadFile', 'uploadFile', '1.0.0', MAX_VERSION, ['url', 'files']);
105registerJsApi('request.downloadFile', 'downloadFile', '1.0.0', MAX_VERSION, ['url']);
106registerJsApi('connection.getNetworkType', 'getNetworkType', '1.0.0', MAX_VERSION, []);
107registerJsApi('location.getLocation', 'getLocation', '1.0.0', MAX_VERSION, []);
108
109@Component
110export struct AtomicServiceWeb {
111  public src: ResourceStr | undefined = undefined;
112  public navPathStack?: NavPathStack;
113  @Prop mixedMode?: MixedMode;
114  @Prop darkMode?: WebDarkMode;
115  @Prop forceDarkAccess?: boolean;
116  @ObjectLink controller: AtomicServiceWebController;
117  public onMessage?: Callback<OnMessageEvent> = () => {
118  };
119  public onErrorReceive?: Callback<OnErrorReceiveEvent> = () => {
120  };
121  public onHttpErrorReceive?: Callback<OnHttpErrorReceiveEvent> = () => {
122  };
123  public onPageBegin?: Callback<OnPageBeginEvent> = () => {
124  };
125  public onPageEnd?: Callback<OnPageEndEvent> = () => {
126  };
127  public onProgressChange?: Callback<OnProgressChangeEvent> = () => {
128  };
129  public onControllerAttached?: VoidCallback;
130  public onLoadIntercept?: Callback<OnLoadInterceptEvent, boolean>;
131  private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
132  private webViewController: web_webview.WebviewController = new web_webview.WebviewController();
133  private schemeHandler: web_webview.WebSchemeHandler = new web_webview.WebSchemeHandler();
134  private atomicService?: AtomicService;
135  private atomicServiceProxy?: AtomicServiceProxy;
136
137  aboutToAppear(): void {
138    if (!this.atomicService) {
139      this.atomicService = new AtomicServiceApi(this.context, this.navPathStack, this.onMessage);
140      this.atomicServiceProxy = new AtomicServiceProxy(this.atomicService);
141    }
142
143    try {
144      let bundleInfo: bundleManager.BundleInfo = bundleManager.getBundleInfoForSelfSync(
145        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
146      if (bundleInfo?.appInfo?.appProvisionType === 'debug') {
147        console.log(`AtomicServiceWeb setWebDebuggingAccess`);
148        web_webview.WebviewController.setWebDebuggingAccess(true);
149      }
150    } catch (err) {
151      console.error(`AtomicServiceWeb set Web Debug Mode failed, code is ${err.code}, message is ${err.message}`);
152    }
153  }
154
155  aboutToDisappear(): void {
156    this.atomicService?.notifyMessage();
157  }
158
159  build() {
160    Web({ src: this.src, controller: this.webViewController })
161      .zoomAccess(false)
162      .allowWindowOpenMethod(false)
163      .domStorageAccess(true)
164      .layoutMode(WebLayoutMode.NONE)
165      .mixedMode(this.mixedMode)
166      .darkMode(this.darkMode)
167      .forceDarkAccess(this.forceDarkAccess)
168      .onErrorReceive((event: OnErrorReceiveEvent) => this.onCommonCallBack('onErrorReceive', event,
169        this.onErrorReceive))
170      .onHttpErrorReceive((event: OnHttpErrorReceiveEvent) => this.onCommonCallBack('onHttpErrorReceive', event,
171        this.onHttpErrorReceive))
172      .onPageBegin((event: OnPageBeginEvent) => this.onCommonCallBack('onPageBegin', event, this.onPageBegin))
173      .onPageEnd((event: OnPageEndEvent) => this.onCommonCallBack('onPageEnd', event, this.onPageEnd))
174      .onProgressChange((event: OnProgressChangeEvent) => this.onCommonCallBack('onProgressChange', event,
175        this.onProgressChange))
176      .onControllerAttached(() => {
177        this.registerJavaScriptProxy();
178        this.schemeHandler.onRequestStart((request: web_webview.WebSchemeHandlerRequest) => {
179          return !this.checkUrl(request.getRequestUrl());
180        });
181        this.webViewController.setWebSchemeHandler('https', this.schemeHandler);
182        this.initAtomicServiceWebController();
183        if (this.onControllerAttached) {
184          try {
185            this.onControllerAttached();
186          } catch (error) {
187            console.error(`AtomicServiceWeb onControllerAttached failed, code is ${error.code}, message is ${error.message}`);
188          }
189        }
190      })
191      .onOverrideUrlLoading((webResourceRequest: WebResourceRequest) => {
192        return !this.checkUrl(webResourceRequest.getRequestUrl());
193      })
194      .onLoadIntercept(event => {
195        let checkResult = !this.checkUrl(event.data.getRequestUrl());
196        if (!checkResult && this.onLoadIntercept) {
197          try {
198            return this.onLoadIntercept(event);
199          } catch (error) {
200            console.error(`AtomicServiceWeb onLoadIntercept failed, code is ${error.code}, message is ${error.message}`);
201            return true;
202          }
203        }
204        return checkResult
205      })
206  }
207
208  onCommonCallBack<T>(method: string, event: T, callback?: (event: T) => void): void {
209    try {
210      callback && callback(event);
211    } catch (error) {
212      console.error(`AtomicServiceWeb ${method} failed, code is ${error.code}, message is ${error.message}`);
213    }
214  }
215
216  registerJavaScriptProxy(): void {
217    try {
218      this.webViewController.registerJavaScriptProxy(this.atomicServiceProxy, JAVA_SCRIPT_PROXY_OBJECT_NAME,
219        JAVA_SCRIPT_PROXY_API_NAME_LIST);
220    } catch (error) {
221      let e: BusinessError = error as BusinessError;
222      console.error(`AtomicServiceWeb registerJavaScriptProxy failed, code is ${e.code}, message is ${e.message}`);
223    }
224  }
225
226  initAtomicServiceWebController(): void {
227    if (!this.controller) {
228      return;
229    }
230    this.controller.setWebviewController(this.webViewController);
231  }
232
233  cutUrl(url: string): string {
234    if (url) {
235      let index: number = url.indexOf('?');
236      if (index > -1) {
237        return url.substring(0, index);
238      }
239    }
240    return url;
241  }
242
243  checkUrl(url: string): boolean {
244    if (!url) {
245      return false;
246    }
247    if (url.startsWith('resource://rawfile')) {
248      return true;
249    }
250    url = this.cutUrl(url);
251    console.log(`AtomicServiceWebLog checkUrl url=${url}`);
252    return true;
253  }
254}
255
256@Observed
257export class AtomicServiceWebController {
258  private webViewController?: web_webview.WebviewController;
259
260  setWebviewController(webViewController: web_webview.WebviewController): void {
261    this.webViewController = webViewController;
262  }
263
264  checkWebviewController(): void {
265    if (!this.webViewController) {
266      const error: BusinessError<string> = {
267        name: '',
268        message: 'Init error. The AtomicServiceWebController must be associated with a AtomicServiceWeb component.',
269        code: 17100001,
270      }
271      throw error as Error;
272    }
273  }
274
275  getUserAgent(): string | undefined {
276    this.checkWebviewController();
277    return this.webViewController?.getUserAgent();
278  }
279
280  getCustomUserAgent(): string | undefined {
281    this.checkWebviewController();
282    return this.webViewController?.getCustomUserAgent();
283  }
284
285  setCustomUserAgent(userAgent: string): void {
286    this.checkWebviewController();
287    this.webViewController?.setCustomUserAgent(userAgent);
288  }
289
290  accessForward(): boolean | undefined {
291    this.checkWebviewController();
292    return this.webViewController?.accessForward();
293  }
294
295  accessBackward(): boolean | undefined {
296    this.checkWebviewController();
297    return this.webViewController?.accessBackward();
298  }
299
300  accessStep(step: number): boolean | undefined {
301    this.checkWebviewController();
302    return this.webViewController?.accessStep(step);
303  }
304
305  forward(): void {
306    this.checkWebviewController();
307    this.webViewController?.forward();
308  }
309
310  backward(): void {
311    this.checkWebviewController();
312    this.webViewController?.backward();
313  }
314
315  refresh(): void {
316    this.checkWebviewController();
317    this.webViewController?.refresh();
318  }
319
320  loadUrl(url: string | Resource, headers?: Array<WebHeader>): void {
321    this.checkWebviewController();
322    if (headers) {
323      this.webViewController?.loadUrl(url, headers);
324    } else {
325      this.webViewController?.loadUrl(url);
326    }
327  }
328}
329
330class AtomicServiceProxy {
331  private atomicService: AtomicService;
332
333  constructor(atomicService: AtomicService) {
334    this.atomicService = atomicService;
335  }
336
337  invokeJsApi<T>(apiNameAlias: string, options: BaseOptions<T>): void {
338    try {
339      options = options || {};
340      if (!apiNameAlias || !ATOMIC_SERVICE_JS_API_MAP.has(apiNameAlias)) {
341        this.atomicService.errorWithCodeAndMsg(JS_API_INVALID_INVOKE_ERROR, options);
342        return;
343      }
344      let jsApiConfig: JsApiConfig | undefined = ATOMIC_SERVICE_JS_API_MAP.get(apiNameAlias);
345      if (!this.atomicService.checkRequiredFieldInOptions(jsApiConfig, options)) {
346        return;
347      }
348      let atomicService: object = this.atomicService;
349      atomicService[jsApiConfig?.apiName as string](options);
350    } catch (err) {
351      this.atomicService.error(err, options);
352    }
353  }
354}
355
356class AtomicService {
357  protected context: common.UIAbilityContext;
358  protected navPathStack?: NavPathStack;
359  protected messageDataList: object[] = [];
360  protected onMessage: (event: OnMessageEvent) => void = () => {
361  };
362
363  constructor(context: common.UIAbilityContext, navPathStack?: NavPathStack,
364    onMessage?: (event: OnMessageEvent) => void) {
365    this.context = context;
366    this.navPathStack = navPathStack;
367    this.onMessage = onMessage ? onMessage : this.onMessage;
368  }
369
370  success<T>(res: T, options: BaseOptions<T>): void {
371    try {
372      options?.callback && options?.callback(undefined, res);
373    } catch (err) {
374      this.consoleError(`callback error, code is ${err.code}, message is ${err.message}`);
375    }
376  }
377
378  error<T>(err: BusinessError, options: BaseOptions<T>,): void {
379    try {
380      options?.callback && options?.callback(new AsError(err.code ? err.code : SYSTEM_INTERNAL_ERROR.code,
381        err.message ? err.message : SYSTEM_INTERNAL_ERROR.message));
382    } catch (err) {
383      this.consoleError(`callback error, code is ${err.code}, message is ${err.message}`);
384    }
385  }
386
387  errorWithCodeAndMsg<T>(error: AsError, options: BaseOptions<T>): void {
388    try {
389      options?.callback && options?.callback(error);
390    } catch (err) {
391      this.consoleError(`callback error, code is ${err.code}, message is ${err.message}`);
392    }
393  }
394
395  consoleLog(msg: string): void {
396    if (LOG_ENABLE) {
397      console.log(`${LOG_PREFIX} ${msg}`);
398    }
399  }
400
401  consoleError(msg: string): void {
402    if (LOG_ENABLE) {
403      console.error(`${LOG_PREFIX} ${msg}`);
404    }
405  }
406
407  logOptions<T>(name: string, options: BaseOptions<T>): void {
408    this.consoleLog(`${name} options=${JSON.stringify(options)}`);
409  }
410
411  checkParamRequired<V, T>(paramKey: string, paramValue: V, options: BaseOptions<T>): boolean {
412    if (paramValue === undefined || paramValue === null || paramValue === '') {
413      this.errorWithCodeAndMsg(new AsError(PARAM_REQUIRED_ERROR_CODE, `Param ${paramKey} is required.`), options);
414      return false;
415    }
416    return true;
417  }
418
419  checkNumberParamPositive<T>(paramKey: string, paramValue: number, options: BaseOptions<T>): boolean {
420    if (paramValue <= 0) {
421      this.errorWithCodeAndMsg(new AsError(PARAM_NUMBER_POSITIVE_ERROR_CODE,
422        `Param ${paramKey} must be a positive number.`), options);
423      return false;
424    }
425    return true;
426  }
427
428  checkRequiredFieldInOptions<T>(jsApiConfig: JsApiConfig | undefined, options: BaseOptions<T>): boolean {
429    if (!jsApiConfig) {
430      return false;
431    }
432    if (!jsApiConfig.requiredFieldNames) {
433      return true;
434    }
435    let obj: object = options;
436    for (let i = 0; i < jsApiConfig.requiredFieldNames.length; i++) {
437      let fieldName: string = jsApiConfig.requiredFieldNames[i];
438      if (!this.checkParamRequired(fieldName, obj[fieldName], options)) {
439        return false;
440      }
441    }
442    return true;
443  }
444
445  checkRouterMode<T>(mode: string | undefined, options: BaseOptions<T>): boolean {
446    if (!mode || mode === 'Single' || mode === 'Standard') {
447      return true;
448    }
449    this.errorWithCodeAndMsg(ROUTER_PARAM_MODE_INVALID_ERROR, options);
450    return false;
451  }
452
453  parseRouterMode(routerMode?: string): router.RouterMode {
454    return routerMode === 'Single' ? router.RouterMode.Single : router.RouterMode.Standard;
455  }
456
457  getRouterIndexByDelta(delta: number): number {
458    let length: number = Number.parseInt(router.getLength());
459    for (let i = length; i > 0; i--) {
460      let state = router.getStateByIndex(i);
461      if (state?.name && delta-- == 0) {
462        return i;
463      }
464    }
465    return 1;
466  }
467
468  checkBackUrlExists<T>(url: string, options: BaseOptions<T>): boolean {
469    let length: number = Number.parseInt(router.getLength());
470    for (let i = length; i > 0; i--) {
471      let state = router.getStateByIndex(i);
472      if (state?.name) {
473        let stateUrl: string = state?.path + state?.name;
474        if (stateUrl === url) {
475          return true;
476        }
477      }
478    }
479    this.errorWithCodeAndMsg(BACK_URL_NOT_EXIST_OR_OPENED_ERROR, options);
480    return false;
481  }
482
483  checkNavPathStack<T>(apiName: string, options: BaseOptions<T>): boolean {
484    if (!this.navPathStack) {
485      this.errorWithCodeAndMsg(new AsError(NAV_PATH_STACK_NOT_EXIST_ERROR_CODE,
486        `Current page is not NavDestination, not support ${apiName}().`), options);
487      return false;
488    }
489    return true;
490  }
491
492  getNavPathIndexByDelta(delta: number): number {
493    let pathStack: string[] | undefined = this.navPathStack?.getAllPathName();
494    if (!pathStack || pathStack.length == 0) {
495      return -1;
496    }
497    return pathStack.length > delta ? (pathStack.length - delta - 1) : -1;
498  }
499
500  onPopHandler(popInfo: PopInfo, onPop?: (event: OnPopEvent) => void): void {
501    if (!popInfo?.info || !onPop) {
502      return;
503    }
504    onPop(new OnPopEvent(popInfo.info.name, popInfo.info.param as object, popInfo.result));
505  }
506
507  getCurrentNavPathInfo(): NavPathInfo {
508    let navPathStack: Array<string> | undefined = this.navPathStack?.getAllPathName();
509    let navPathInfo: NavPathInfo = (navPathStack && navPathStack.length > 0) ?
510      new NavPathInfo(navPathStack[navPathStack.length - 1], navPathStack.length - 1) : new NavPathInfo(undefined, -1);
511    if (navPathInfo.index >= 0) {
512      navPathInfo.param = this.navPathStack?.getParamByIndex(navPathInfo.index) as object;
513    }
514    return navPathInfo;
515  }
516
517  notifyMessage(): void {
518    if (this.messageDataList.length <= 0) {
519      return;
520    }
521    try {
522      this.onMessage(new OnMessageEvent(this.messageDataList));
523    } catch (err) {
524      this.consoleError(`onMessage failed, code is ${err.code}, message is ${err.message}`);
525    }
526    this.messageDataList = [];
527  }
528
529  isJsApiEnable(jsApiConfig?: JsApiConfig): boolean {
530    if (!jsApiConfig) {
531      return false;
532    }
533    if (this.compareVersion(jsApiConfig.minVersion, ATOMIC_SERVICE_JS_SDK_CURRENT_VERSION) &&
534    this.compareVersion(ATOMIC_SERVICE_JS_SDK_CURRENT_VERSION, jsApiConfig.maxVersion)) {
535      return true;
536    }
537    return false;
538  }
539
540  compareVersion(lowVersion: string, highVersion: string): boolean {
541    if (!lowVersion || !highVersion) {
542      return false;
543    }
544    let v1 = lowVersion.split('.').map(m => Number.parseInt(m));
545    let v2 = highVersion.split('.').map(m => Number.parseInt(m));
546    const maxLength = Math.max(v1.length, v2.length);
547    for (let i = 0; i < maxLength; i++) {
548      if (v1[i] < v2[i]) {
549        return true;
550      } else if (v1[i] > v2[i]) {
551        return false;
552      }
553    }
554    if (v1.length < v2.length) {
555      return true;
556    }
557    if (v1.length > v2.length) {
558      return false;
559    }
560    return true;
561  }
562
563  getUri(uriOrFilePath: string): string {
564    if (!uriOrFilePath || uriOrFilePath.startsWith('file://')) {
565      return uriOrFilePath;
566    }
567    return fileUri.getUriFromPath(uriOrFilePath);
568  }
569
570  async checkUploadFile(options: UploadFileOptions): Promise<CheckUploadFileResult> {
571    if (!options.files || options.files.length <= 0) {
572      this.errorWithCodeAndMsg(UPLOAD_IMAGE_FILES_REQUIRED_ERROR, options);
573      return new CheckUploadFileResult(false);
574    }
575    let uriMap: Map<string, string> = new Map();
576    for (let i = 0; i < options.files?.length; i++) {
577      let file: UploadFile = options.files[i];
578      if (!file.uri) {
579        this.errorWithCodeAndMsg(UPLOAD_IMAGE_FILES_URI_REQUIRED_ERROR, options);
580        return new CheckUploadFileResult(false);
581      }
582      if (!file.uri.startsWith('file://') && !fs.accessSync(file.uri, fs.AccessModeType.EXIST)) {
583        this.errorWithCodeAndMsg(new AsError(UPLOAD_IMAGE_FILE_NOT_EXIST_ERROR_CODE,
584          `File uri ${file.uri} is not exist.`), options);
585        return new CheckUploadFileResult(false);
586      }
587      let originUri: string = file.uri;
588      let uploadUri: string = file.uri;
589      if (uploadUri.indexOf(UPLOAD_IMAGE_CACHE_DIR) < 0) {
590        let srcUri: string = uploadUri.startsWith('file://') ? uploadUri : fileUri.getUriFromPath(file.uri);
591        uploadUri = this.context.cacheDir + '/' + uploadUri.substring(uploadUri.lastIndexOf('/') + 1);
592        try {
593          await fs.copy(srcUri, fileUri.getUriFromPath(uploadUri))
594        } catch (err) {
595          this.errorWithCodeAndMsg(UPLOAD_FILE_ERROR, options);
596          return new CheckUploadFileResult(false);
597        }
598      }
599      file.uri = 'internal://' + uploadUri.substring(uploadUri.indexOf(UPLOAD_IMAGE_CACHE_DIR) + 1);
600      uriMap.set(uploadUri, originUri);
601    }
602    return new CheckUploadFileResult(true, uriMap);
603  }
604
605  convertToRequestData(data?: UploadRequestData[]): request.RequestData[] {
606    let requestData: request.RequestData[] = [];
607    if (data) {
608      data.forEach(item => {
609        if (!item.name || !item.value) {
610          return;
611        }
612        requestData.push({ name: item.name, value: item.value });
613      });
614    }
615    return requestData;
616  }
617
618  convertToFile(files?: UploadFile[]): request.File[] {
619    let requestFiles: request.File[] = [];
620    if (files) {
621      files.forEach(item => {
622        requestFiles.push({
623          filename: item.filename,
624          name: item.name,
625          uri: item.uri,
626          type: item.type
627        });
628      });
629    }
630    return requestFiles;
631  }
632
633  handleUploadFileResult(taskStateArray: Array<request.TaskState>, uriMap: Map<string, string>,
634    options: UploadFileOptions): void {
635    let taskStates: UploadFileTaskState[] = [];
636    if (taskStateArray) {
637      taskStateArray.forEach(taskState => {
638        let path: (string | undefined) = taskState.path ? uriMap.get(taskState.path) : taskState.path;
639        taskStates.push(new UploadFileTaskState(path ? path : taskState.path, taskState.responseCode,
640          taskState.message));
641      });
642    }
643    this.success(new UploadFileResult(taskStates), options);
644  }
645
646  parseFileNameFromUrl(url?: string): string {
647    if (!url) {
648      return '';
649    }
650    let http: string = url.split('?')[0];
651    if (http.indexOf('/') < 0) {
652      return '';
653    }
654    let index: number = http.lastIndexOf('/');
655    if (index == (http.length - 1)) {
656      return '';
657    }
658    return http.substring(index + 1);
659  }
660
661  saveDownloadFile(filePath: string, fileName: string, options: DownloadFileOptions,
662    callback: (uri: string) => void): void {
663    let documentPicker = new filePicker.DocumentViewPicker();
664    documentPicker.save({
665      newFileNames: [fileName]
666    }).then(res => {
667      let uri: string = res[0];
668      fs.copy(fileUri.getUriFromPath(filePath), uri).then(() => {
669        callback && callback(uri);
670      }).catch((err: BusinessError) => {
671        this.error(err, options);
672      });
673    }).catch((err: BusinessError) => {
674      this.error(err, options);
675    });
676  }
677
678  checkAccessToken(permissionName: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
679    let bundleInfo: bundleManager.BundleInfo = bundleManager.getBundleInfoForSelfSync(
680      bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
681    let tokenId: number = bundleInfo.appInfo.accessTokenId;
682    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
683    return atManager.checkAccessToken(tokenId, permissionName);
684  }
685
686  checkPermissions(permissionName: Permissions, grantCallback: (err?: BusinessError) => void): void {
687    this.checkAccessToken(permissionName).then(grantStatus => {
688      if (grantStatus == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
689        grantCallback(undefined);
690      } else {
691        let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
692        atManager.requestPermissionsFromUser(this.context, [permissionName]).then(permissionRequestResult => {
693          for (let i = 0; i < permissionRequestResult.authResults.length; i++) {
694            if (permissionRequestResult.authResults[i] != 0) {
695              const error: BusinessError<void> = {
696                name: '',
697                message: `RequestPermissionsFromUser error. authResult: ${permissionRequestResult.authResults[i]}.`,
698                code: PERMISSION_LOCATION_USER_REFUSED_ERROR
699              };
700              grantCallback(error);
701              return;
702            }
703          }
704          grantCallback(undefined);
705        }).catch((err: BusinessError) => {
706          grantCallback(err);
707        });
708      }
709    }).catch((err: BusinessError) => {
710      grantCallback(err);
711    });
712  }
713}
714
715class AtomicServiceApi extends AtomicService {
716  constructor(context: common.UIAbilityContext, navPathStack?: NavPathStack,
717    onMessage?: (event: OnMessageEvent) => void) {
718    super(context, navPathStack, onMessage);
719  }
720
721  pushUrl(options: PushUrlOptions): void {
722    if (!this.checkRouterMode(options.mode, options)) {
723      return;
724    }
725    router.pushUrl({ url: options.url, params: options.params }, this.parseRouterMode(options.mode)).then(() => {
726      this.success(new PushUrlResult(), options);
727    }).catch((err: BusinessError) => {
728      this.error(err, options);
729    });
730  }
731
732  replaceUrl(options: ReplaceUrlOptions): void {
733    if (!this.checkRouterMode(options.mode, options)) {
734      return;
735    }
736    router.replaceUrl({ url: options.url, params: options.params }, this.parseRouterMode(options.mode)).then(() => {
737      this.success(new ReplaceUrlResult(), options);
738    }).catch((err: BusinessError) => {
739      this.error(err, options);
740    });
741  }
742
743  backUrl(options: BackUrlOptions): void {
744    if (options.url) {
745      if (!this.checkBackUrlExists(options.url, options)) {
746        return;
747      }
748      router.back({ url: options.url, params: options.params });
749      this.success(new BackUrlResult(), options);
750    } else if (options.index || options.index === 0) {
751      if (!this.checkNumberParamPositive('index', options.index, options)) {
752        return;
753      }
754      router.back(options.index, options.params);
755      this.success(new BackUrlResult(), options);
756    } else if (options.delta || options.delta === 0) {
757      if (!this.checkNumberParamPositive('delta', options.delta, options)) {
758        return;
759      }
760      router.back(this.getRouterIndexByDelta(options.delta), options.params);
761      this.success(new BackUrlResult(), options);
762    } else {
763      router.back();
764      this.success(new BackUrlResult(), options);
765    }
766  }
767
768  clearUrl(options: ClearUrlOptions): void {
769    router.clear();
770    this.success(new ClearUrlResult(), options);
771  }
772
773  pushPath(options: PushPathOptions): void {
774    if (!this.checkNavPathStack('navPathStack.pushPath', options)) {
775      return;
776    }
777    this.navPathStack?.pushPath({
778      name: options.name,
779      param: options.param,
780      onPop: popInfo => this.onPopHandler(popInfo, options.onPop)
781    }, options.animated);
782    this.success(new PushPathResult(), options);
783  }
784
785  replacePath(options: ReplacePathOptions): void {
786    if (!this.checkNavPathStack('navPathStack.replacePath', options)) {
787      return;
788    }
789    this.navPathStack?.replacePath({
790      name: options.name,
791      param: options.param,
792      onPop: popInfo => this.onPopHandler(popInfo, options.onPop)
793    }, options.animated);
794    this.success(new ReplacePathResult(), options);
795  }
796
797  popPath(options: PopPathOptions): void {
798    if (!this.checkNavPathStack('navPathStack.pop', options)) {
799      return;
800    }
801    if (options.name) {
802      let index: number | undefined = this.navPathStack?.popToName(options.name, options.result, options.animated);
803      if (index === undefined || index === -1) {
804        this.errorWithCodeAndMsg(POP_PATH_NAME_NOT_EXIST_ERROR, options);
805        return;
806      }
807    } else if (options.index || options.index === 0) {
808      if (options.index < -1) {
809        this.errorWithCodeAndMsg(POP_PATH_PARAM_INDEX_INVALID_ERROR, options);
810        return;
811      }
812      if (options.index > this.getCurrentNavPathInfo().index) {
813        this.errorWithCodeAndMsg(POP_PATH_INDEX_OUT_OF_RANGE_ERROR, options);
814        return;
815      }
816      this.navPathStack?.popToIndex(options.index, options.result, options.animated);
817    } else if (options.delta || options.delta === 0) {
818      if (!this.checkNumberParamPositive('delta', options.delta, options)) {
819        return;
820      }
821      this.navPathStack?.popToIndex(this.getNavPathIndexByDelta(options.delta), options.result, options.animated);
822    } else {
823      this.navPathStack?.pop(options.result, options.animated);
824    }
825    let navPathInfo: NavPathInfo = this.getCurrentNavPathInfo();
826    this.success(new PopPathResult(navPathInfo.name, navPathInfo.index, navPathInfo.param), options);
827  }
828
829  clearPath(options: ClearPathOptions): void {
830    if (!this.checkNavPathStack('navPathStack.clear', options)) {
831      return;
832    }
833    this.navPathStack?.clear(options.animated);
834    this.success(new ClearPathResult(), options);
835  }
836
837  postMessage(options: PostMessageOptions): void {
838    options.data && this.messageDataList.push(options.data);
839    this.success(new PostMessageResult(), options);
840  }
841
842  getEnv(options: GetEnvOptions): void {
843    let res: GetEnvResult = new GetEnvResult();
844    res.deviceType = deviceInfo.deviceType;
845    res.brand = deviceInfo.brand;
846    res.productModel = deviceInfo.productModel;
847    res.osFullName = deviceInfo.osFullName;
848    this.success(res, options);
849  }
850
851  checkJsApi(options: CheckJsApiOptions): void {
852    let res: Map<string, boolean> = new Map();
853    options.jsApiList?.forEach(jsApi => {
854      res[jsApi] = this.isJsApiEnable(ATOMIC_SERVICE_JS_API_MAP.get(jsApi));
855    });
856    this.success(new CheckJsApiResult(res), options);
857  }
858
859  pickCamera(options: PickCameraOptions): void {
860    picker.pick(this.context, options.mediaTypes as Array<picker.PickerMediaType>, {
861      cameraPosition: options.cameraPosition,
862      saveUri: options.saveUri,
863      videoDuration: options.videoDuration
864    }).then((pickerResult: picker.PickerResult) => {
865      this.success(new PickCameraResult(pickerResult.resultCode, pickerResult.resultUri, pickerResult.mediaType),
866        options);
867    }).catch((err: BusinessError) => {
868      this.error(err, options);
869    });
870  }
871
872  selectPhoto(options: SelectPhotoOptions): void {
873    let photoViewPicker = new photoAccessHelper.PhotoViewPicker();
874    photoViewPicker.select({
875      MIMEType: options.mimeType as photoAccessHelper.PhotoViewMIMETypes,
876      maxSelectNumber: options.maxSelectNumber,
877      isPhotoTakingSupported: options.isPhotoTakingSupported,
878      isEditSupported: options.isEditSupported,
879      isSearchSupported: options.isSearchSupported,
880      recommendationOptions: {
881        recommendationType: options.recommendationType
882      },
883      preselectedUris: options.preselectedUris
884    }).then((selectResult: photoAccessHelper.PhotoSelectResult) => {
885      this.success(new SelectPhotoResult(selectResult.photoUris, selectResult.isOriginalPhoto), options);
886    }).catch((err: BusinessError) => {
887      this.error(err, options);
888    });
889  }
890
891  openPreview(options: OpenPreviewOptions): void {
892    let uri: string = this.getUri(options.uri as string);
893    filePreview.canPreview(this.context, uri).then((res: boolean) => {
894      if (!res) {
895        this.errorWithCodeAndMsg(IMAGE_CAN_NOT_PREVIEW_ERROR, options);
896        return;
897      }
898      filePreview.openPreview(this.context, {
899        uri: uri,
900        mimeType: options.mimeType as string,
901        title: options.title
902      }).then(() => {
903        this.success(new OpenPreviewResult(), options);
904      }).catch((err: BusinessError) => {
905        this.error(err, options);
906      });
907    }).catch((err: BusinessError) => {
908      this.error(err, options);
909    });
910  }
911
912  uploadFile(options: UploadFileOptions): void {
913    this.checkUploadFile(options).then(res => {
914      if (!res.checkResult) {
915        return;
916      }
917      let uploadConfig: request.UploadConfig = {
918        url: options.url as string,
919        header: options.header as object,
920        method: options.method as string,
921        files: this.convertToFile(options.files),
922        data: this.convertToRequestData(options.data)
923      };
924      request.uploadFile(this.context, uploadConfig).then((uploadTask: request.UploadTask) => {
925        uploadTask.on('complete', (taskStateArray: Array<request.TaskState>) => {
926          this.handleUploadFileResult(taskStateArray, res.uriMap as Map<string, string>, options);
927        });
928        uploadTask.on('fail', (taskStateArray: Array<request.TaskState>) => {
929          this.handleUploadFileResult(taskStateArray, res.uriMap as Map<string, string>, options);
930        });
931      }).catch((err: BusinessError) => {
932        this.error(err, options);
933      });
934    }).catch((err: BusinessError) => {
935      this.error(err, options);
936    });
937  }
938
939  downloadFile(options: DownloadFileOptions): void {
940    let fileName: string = options.fileName ? options.fileName : this.parseFileNameFromUrl(options.url);
941    let cacheFileName: string = `${util.generateRandomUUID().replaceAll('-', '')}`;
942    let filePath: string = `${this.context.cacheDir}/${cacheFileName}`;
943    request.downloadFile(this.context, {
944      url: options.url,
945      header: options.header ? options.header : new Object(),
946      filePath: filePath,
947      enableMetered: options.enableMetered,
948      enableRoaming: options.enableRoaming,
949      networkType: options.networkType,
950      background: false
951    }).then((downloadTask: request.DownloadTask) => {
952      downloadTask.on('complete', () => {
953        this.saveDownloadFile(filePath, fileName, options, uri => {
954          this.success(new DownloadFileResult(uri), options);
955        });
956      });
957      downloadTask.on('fail', errCode => {
958        this.errorWithCodeAndMsg(new AsError(errCode, 'File download fail.'), options);
959      });
960    }).catch((err: BusinessError) => {
961      this.error(err, options);
962    });
963  }
964
965  getNetworkType(options: GetNetworkTypeOptions): void {
966    connection.getDefaultNet().then(netHandle => {
967      if (!netHandle || netHandle.netId === 0) {
968        this.errorWithCodeAndMsg(NETWORK_NO_ACTIVE_ERROR, options);
969        return;
970      }
971      connection.getNetCapabilities(netHandle).then(netCapabilities => {
972        let res: GetNetworkTypeResult = new GetNetworkTypeResult(netCapabilities.bearerTypes,
973          netCapabilities.networkCap, netCapabilities.linkUpBandwidthKbps, netCapabilities.linkDownBandwidthKbps);
974        this.success(res, options);
975      }).catch((err: BusinessError) => {
976        this.error(err, options);
977      });
978    }).catch((err: BusinessError) => {
979      this.error(err, options);
980    });
981  }
982
983  getLocation(options: GetLocationOptions): void {
984    this.checkPermissions(PERMISSION_APPROXIMATELY_LOCATION, err => {
985      if (err) {
986        this.error(err, options);
987        return;
988      }
989      geoLocationManager.getCurrentLocation({
990        priority: options.priority,
991        scenario: options.scenario,
992        maxAccuracy: options.maxAccuracy,
993        timeoutMs: options.timeoutMs
994      }).then(location => {
995        let res: GetLocationResult = new GetLocationResult(location.latitude, location.longitude, location.altitude,
996          location.accuracy, location.speed, location.timeStamp, location.direction, location.timeSinceBoot,
997          location.additions, location.additionSize);
998        this.success(res, options);
999      }).catch((err: BusinessError) => {
1000        this.error(err, options);
1001      });
1002    });
1003  }
1004}
1005
1006class NavPathInfo {
1007  public name: string | undefined;
1008  public index: number;
1009  public param?: object;
1010
1011  constructor(name: string | undefined, index: number) {
1012    this.name = name;
1013    this.index = index;
1014  }
1015}
1016
1017class CheckUploadFileResult {
1018  public checkResult: boolean;
1019  public uriMap?: Map<string, string>;
1020
1021  constructor(checkResult: boolean, uriMap?: Map<string, string>) {
1022    this.checkResult = checkResult;
1023    this.uriMap = uriMap;
1024  }
1025}
1026
1027class BaseOptions<T> {
1028  public callback?: (err: AsError | undefined, res?: T) => void;
1029}
1030
1031class PushUrlOptions extends BaseOptions<PushUrlResult> {
1032  public url?: string;
1033  public params?: object;
1034  public mode?: string;
1035}
1036
1037class PushUrlResult {
1038}
1039
1040class ReplaceUrlOptions extends BaseOptions<ReplaceUrlResult> {
1041  public url?: string;
1042  public params?: object;
1043  public mode?: string;
1044}
1045
1046class ReplaceUrlResult {
1047}
1048
1049class BackUrlOptions extends BaseOptions<BackUrlResult> {
1050  public url?: string;
1051  public index?: number;
1052  public delta?: number;
1053  public params?: object;
1054}
1055
1056class BackUrlResult {
1057}
1058
1059class ClearUrlOptions extends BaseOptions<ClearUrlResult> {
1060}
1061
1062class ClearUrlResult {
1063}
1064
1065class OnPopEvent {
1066  public name?: string;
1067  public param?: object;
1068  public result?: object;
1069
1070  constructor(name?: string, param?: object, result?: object) {
1071    this.name = name;
1072    this.param = param;
1073    this.result = result;
1074  }
1075}
1076
1077class PushPathOptions extends BaseOptions<PushPathResult> {
1078  public name?: string;
1079  public param?: object;
1080  public animated?: boolean;
1081  public onPop?: (event: OnPopEvent) => void;
1082}
1083
1084class PushPathResult {
1085}
1086
1087class ReplacePathOptions extends BaseOptions<ReplacePathResult> {
1088  public name?: string;
1089  public param?: object;
1090  public animated?: boolean;
1091  public onPop?: (event: OnPopEvent) => void;
1092}
1093
1094class ReplacePathResult {
1095}
1096
1097class PopPathOptions extends BaseOptions<PopPathResult> {
1098  public name?: string;
1099  public index?: number;
1100  public delta?: number;
1101  public result?: object;
1102  public animated?: boolean;
1103}
1104
1105class PopPathResult {
1106  public name: string | undefined;
1107  public index: number;
1108  public param?: object;
1109
1110  constructor(name: string | undefined, index: number, param?: object) {
1111    this.name = name;
1112    this.index = index;
1113    this.param = param;
1114  }
1115}
1116
1117class ClearPathOptions extends BaseOptions<ClearPathResult> {
1118  public animated?: boolean;
1119}
1120
1121class ClearPathResult {
1122}
1123
1124class PostMessageOptions extends BaseOptions<PostMessageResult> {
1125  public data?: object;
1126}
1127
1128class PostMessageResult {
1129}
1130
1131export class OnMessageEvent {
1132  public data: object[];
1133
1134  constructor(data: object[]) {
1135    this.data = data;
1136  }
1137}
1138
1139export class OnErrorReceiveEvent {
1140  public request: WebResourceRequest;
1141  public error: WebResourceError;
1142
1143  constructor(request: WebResourceRequest, error: WebResourceError) {
1144    this.request = request;
1145    this.error = error;
1146  }
1147}
1148
1149export class OnHttpErrorReceiveEvent {
1150  public request: WebResourceRequest;
1151  public response: WebResourceResponse;
1152
1153  constructor(request: WebResourceRequest, response: WebResourceResponse) {
1154    this.request = request;
1155    this.response = response;
1156  }
1157}
1158
1159export class OnPageBeginEvent {
1160  public url: string;
1161
1162  constructor(url: string) {
1163    this.url = url;
1164  }
1165}
1166
1167export class OnPageEndEvent {
1168  public url: string;
1169
1170  constructor(url: string) {
1171    this.url = url;
1172  }
1173}
1174
1175export class WebHeader {
1176  public headerKey: string;
1177  public headerValue: string;
1178
1179  constructor(headerKey: string, headerValue: string) {
1180    this.headerKey = headerKey;
1181    this.headerValue = headerValue;
1182  }
1183}
1184
1185class GetEnvOptions extends BaseOptions<GetEnvResult> {
1186}
1187
1188class GetEnvResult {
1189  public deviceType?: string;
1190  public brand?: string;
1191  public productModel?: string;
1192  public osFullName?: string;
1193}
1194
1195class CheckJsApiOptions extends BaseOptions<CheckJsApiResult> {
1196  public jsApiList?: string[];
1197}
1198
1199class CheckJsApiResult {
1200  public checkResult?: Map<string, boolean>;
1201
1202  constructor(checkResult?: Map<string, boolean>) {
1203    this.checkResult = checkResult;
1204  }
1205}
1206
1207class PickCameraOptions extends BaseOptions<PickCameraResult> {
1208  public mediaTypes?: string[];
1209  public cameraPosition?: number;
1210  public saveUri?: string;
1211  public videoDuration?: number;
1212}
1213
1214class PickCameraResult {
1215  public resultCode?: number;
1216  public resultUri?: string;
1217  public mediaType?: string;
1218
1219  constructor(resultCode?: number, resultUri?: string, mediaType?: string) {
1220    this.resultCode = resultCode;
1221    this.resultUri = resultUri;
1222    this.mediaType = mediaType;
1223  }
1224}
1225
1226class SelectPhotoOptions extends BaseOptions<SelectPhotoResult> {
1227  public mimeType?: string;
1228  public maxSelectNumber?: number;
1229  public isPhotoTakingSupported?: boolean;
1230  public isEditSupported?: boolean;
1231  public isSearchSupported?: boolean;
1232  public recommendationType?: number;
1233  public preselectedUris?: string[];
1234}
1235
1236class SelectPhotoResult {
1237  public photoUris?: string[];
1238  public isOriginalPhoto?: boolean;
1239
1240  constructor(photoUris?: string[], isOriginalPhoto?: boolean) {
1241    this.photoUris = photoUris;
1242    this.isOriginalPhoto = isOriginalPhoto;
1243  }
1244}
1245
1246class OpenPreviewOptions extends BaseOptions<OpenPreviewResult> {
1247  public title?: string;
1248  public uri?: string;
1249  public mimeType?: string;
1250}
1251
1252class OpenPreviewResult {
1253}
1254
1255class UploadFileOptions extends BaseOptions<UploadFileResult> {
1256  public url?: string;
1257  public header?: object;
1258  public method?: string;
1259  public files?: UploadFile[];
1260  public data?: UploadRequestData[];
1261}
1262
1263class UploadFile {
1264  public filename: string;
1265  public name: string;
1266  public uri: string;
1267  public type: string;
1268
1269  constructor(filename: string, name: string, uri: string, type: string) {
1270    this.filename = filename;
1271    this.name = name;
1272    this.uri = uri;
1273    this.type = type;
1274  }
1275}
1276
1277class UploadRequestData {
1278  public name?: string;
1279  public value?: string;
1280}
1281
1282class UploadFileResult {
1283  public taskStates?: UploadFileTaskState[];
1284
1285  constructor(taskStates?: UploadFileTaskState[]) {
1286    this.taskStates = taskStates;
1287  }
1288}
1289
1290class UploadFileTaskState {
1291  public path?: string;
1292  public responseCode?: number;
1293  public message?: string;
1294
1295  constructor(path?: string, responseCode?: number, message?: string) {
1296    this.path = path;
1297    this.responseCode = responseCode;
1298    this.message = message;
1299  }
1300}
1301
1302class DownloadFileOptions extends BaseOptions<DownloadFileResult> {
1303  public url?: string;
1304  public header?: object;
1305  public fileName?: string;
1306  public enableMetered?: boolean;
1307  public enableRoaming?: boolean;
1308  public networkType?: number;
1309}
1310
1311class DownloadFileResult {
1312  public uri?: string;
1313
1314  constructor(uri?: string) {
1315    this.uri = uri;
1316  }
1317}
1318
1319class GetNetworkTypeOptions extends BaseOptions<GetNetworkTypeResult> {
1320}
1321
1322class GetNetworkTypeResult {
1323  public bearerTypes: number[];
1324  public networkCap?: number[];
1325  public linkUpBandwidthKbps?: number;
1326  public linkDownBandwidthKbps?: number;
1327
1328  constructor(bearerTypes: number[], networkCap?: number[], linkUpBandwidthKbps?: number,
1329    linkDownBandwidthKbps?: number) {
1330    this.bearerTypes = bearerTypes;
1331    this.networkCap = networkCap;
1332    this.linkUpBandwidthKbps = linkUpBandwidthKbps;
1333    this.linkDownBandwidthKbps = linkDownBandwidthKbps;
1334  }
1335}
1336
1337class GetLocationOptions extends BaseOptions<GetLocationResult> {
1338  public priority?: number;
1339  public scenario?: number;
1340  public maxAccuracy?: number;
1341  public timeoutMs?: number;
1342}
1343
1344class GetLocationResult {
1345  public latitude: number;
1346  public longitude: number;
1347  public altitude: number;
1348  public accuracy: number;
1349  public speed: number;
1350  public timeStamp: number;
1351  public direction: number;
1352  public timeSinceBoot: number;
1353  public additions?: string[] | undefined;
1354  public additionSize?: number;
1355
1356  constructor(latitude: number, longitude: number, altitude: number, accuracy: number, speed: number,
1357    timeStamp: number, direction: number, timeSinceBoot: number, additions?: string[], additionSize?: number) {
1358    this.latitude = latitude;
1359    this.longitude = longitude;
1360    this.altitude = altitude;
1361    this.accuracy = accuracy;
1362    this.speed = speed;
1363    this.timeStamp = timeStamp;
1364    this.direction = direction;
1365    this.timeSinceBoot = timeSinceBoot;
1366    this.additions = additions;
1367    this.additionSize = additionSize;
1368  }
1369}
1370