1/*
2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import fileAccess from '@ohos.file.fileAccess'
17import Logger from '../log/Logger'
18import abilityAccessCtrl from '@ohos.abilityAccessCtrl'
19import { Permissions } from '@ohos.abilityAccessCtrl'
20import FileShare from '@ohos.fileshare'
21import wantConstant from '@ohos.app.ability.wantConstant'
22import { FILE_MANAGER_PREFERENCES, FILE_SUFFIX, SELECT_MODE } from '../constants/Constant'
23import { ArrayUtil } from './ArrayUtil'
24import { getPreferences } from './PreferencesUtil'
25import { ability, Want } from '@kit.AbilityKit'
26import { StartModeOptions } from '../model/StartModeOptions'
27import { PickerWindowType } from '../constants/FilePickerItems'
28import { photoAccessHelper } from '@kit.MediaLibraryKit'
29
30const TAG = 'AbilityCommonUtil'
31
32const BUNDLE_NAME = 'com.ohos.filepicker'
33let photoManageHelper: photoAccessHelper.PhotoAccessHelper = null
34
35/**
36 * picker对外返回的响应码
37 */
38export enum ResultCodePicker {
39  SUCCESS = 0,
40  CANCEL = -1
41}
42
43interface abilityResultInterface {
44  want: Want,
45  resultCode: number
46};
47
48/**
49 * Ability公共工具类
50 */
51namespace AbilityCommonUtil {
52
53  /**
54   * 需要用户授权的权限列表
55   */
56  export const PERMISSION_LIST: Array<Permissions> = [
57    "ohos.permission.MEDIA_LOCATION",
58    "ohos.permission.READ_MEDIA",
59    "ohos.permission.WRITE_MEDIA"
60  ]
61
62  /**
63   * 用来获取startAbility调用方应用uid的key
64   */
65  export const CALLER_UID = 'ohos.aafwk.param.callerUid'
66
67  export const CALLER_BUNDLE_NAME = 'ohos.aafwk.param.callerBundleName'
68
69  /**
70   * 最大选择文件的个数
71   */
72  export const MAX_FILE_PICK_NUM = 500
73
74  /**
75   * 后缀最大长度,包括'.'
76   */
77  export const SUFFIX_MAX_LENGTH: number = 255;
78
79  /**
80   * 三方传入的后缀数组长度最大100
81   */
82  export const SUFFIX_LIST_MAX_LENGTH: number = 100;
83
84  /**
85   * picker对外返回的响应码
86   */
87  export const RESULT_CODE = {
88    SUCCESS: 0,
89    CANCEL: -1
90  }
91
92  export const ABILITY_LIST = {
93    FILE_MANAGER: 'FileManagerAbility',
94    FILE_PICKER: 'FilePickerAbility',
95    PATH_PICKER: 'PathPickerAbility'
96  }
97
98  /**
99   * 拉起Ability时必要的初始化操作
100   */
101  export function init(): Promise<void[]> {
102    const fileAccessHelperPromise = createFileAccessHelper();
103    getPhotoManageHelper();
104    const getRequestPermission = requestPermission();
105    const initData = initLastSelectPath();
106    return Promise.all([fileAccessHelperPromise, getRequestPermission, initData]);
107  }
108
109  /**
110   * 获取FileAccessHelper,用于获取文件和操作文件
111   */
112  export function createFileAccessHelper(): Promise<void> {
113    if (globalThis.fileAccessHelper) {
114      return Promise.resolve()
115    }
116    return new Promise(async (resolve, reject) => {
117      try {
118        let wants = await fileAccess.getFileAccessAbilityInfo()
119        globalThis.fileAcsHelper = fileAccess.createFileAccessHelper(globalThis.abilityContext, wants)
120        // 获取设备根节点信息
121        const rootIterator: fileAccess.RootIterator = await globalThis.fileAcsHelper.getRoots()
122        let rootInfoArr = []
123        let result = rootIterator.next()
124        let isDone = result.done
125        while (!isDone) {
126          const rootInfo: fileAccess.RootInfo = result.value
127          Logger.i(TAG, 'RootInfo: ' + rootInfo.uri + ', ' + rootInfo.deviceType + ', ' + rootInfo.deviceFlags + ', ' +
128          rootInfo.displayName + ',' + rootInfo.relativePath)
129          rootInfoArr.push(rootInfo)
130          result = rootIterator.next()
131          isDone = result.done
132        }
133        globalThis.rootInfoArr = rootInfoArr
134      } catch (err) {
135        Logger.e(TAG, 'createFileAccessHelper fail, error:' + JSON.stringify(err))
136      } finally {
137        resolve()
138      }
139    })
140  }
141
142  /**
143   * Ability初始化时,加载最新保存的路径Uri
144   */
145  export function initLastSelectPath(): Promise<void> {
146    return new Promise((resolve, reject) => {
147      const defaultValue = FILE_MANAGER_PREFERENCES.lastSelectPath.defaultValue;
148      const lastSelectPathKey = FILE_MANAGER_PREFERENCES.lastSelectPath.key;
149      getPreferences(FILE_MANAGER_PREFERENCES.name).then(preferences => {
150        preferences.get(lastSelectPathKey, defaultValue).then((result: string) => {
151          AppStorage.SetOrCreate<string>(lastSelectPathKey, result);
152          resolve();
153          Logger.i(TAG, 'initLastSelectPath result: ' + result);
154        }).catch((error) => {
155          AppStorage.SetOrCreate<string>(lastSelectPathKey, defaultValue);
156          Logger.e(TAG, 'initLastSelectPath preferences.get fail, error:' + JSON.stringify(error));
157          resolve();
158        })
159      }).catch(err => {
160        AppStorage.SetOrCreate<string>(lastSelectPathKey, defaultValue);
161        Logger.e(TAG, 'initLastSelectPath getPreferences fail, error: ' + JSON.stringify(err));
162        resolve();
163      })
164    })
165  }
166
167  /**
168   * 申请文件管理器需用户授权的权限
169   */
170  export function requestPermission(): Promise<void> {
171    let atManager = abilityAccessCtrl.createAtManager()
172    try {
173      return atManager.requestPermissionsFromUser(globalThis.abilityContext, PERMISSION_LIST).then((data) => {
174        if (data.authResults.some(item => item !== 0)) {
175          Logger.e(TAG, 'requestPermissionsFromUser some permission request fail, result:' + JSON.stringify(data))
176        } else {
177          Logger.i(TAG, 'requestPermissionsFromUser success, result:' + JSON.stringify(data))
178        }
179      }).catch((error) => {
180        Logger.e(TAG, 'requestPermissionsFromUser fail, error:' + JSON.stringify(error))
181      })
182    } catch (error) {
183      Logger.e(TAG, 'requestPermissionsFromUser error occurred, error:' + JSON.stringify(error))
184    }
185  }
186
187  /**
188   * uri授权
189   * @param uriList 待授权的uri列表
190   * @param bundleName 授权应用的包名
191   */
192  export function grantUriPermission(uriList: Array<string>, bundleName: string): Promise<boolean> {
193    return new Promise(async (resolve, reject) => {
194      Logger.i(TAG, "grantUriPermission start,grantSize = " + uriList?.length);
195      let grantSuccessCount: number = 0;
196      for (let uri of uriList) {
197        try {
198          await FileShare.grantUriPermission(
199            uri,
200            bundleName,
201            wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION);
202          grantSuccessCount++;
203        } catch (error) {
204          resolve(false);
205          Logger.e(TAG,
206            `grantUriPermission fail,grantSuccessCount:${grantSuccessCount}}, uri: ${uri}, error: ${JSON.stringify(error)}`);
207          return;
208        }
209      }
210      Logger.i(TAG, "grantUriPermission end,grantSuccessCount = " + grantSuccessCount);
211      resolve(true)
212    })
213  }
214
215  /**
216   * 文件选择完成,返回uri列表
217   * @param resultCode
218   * @param result
219   * @param message
220   */
221  export async function terminateFilePicker(result: string[] = [],
222    resultCode: number = ResultCodePicker.SUCCESS, startModeOptions: StartModeOptions): Promise<void> {
223    Logger.i(TAG, 'enter terminateFilePicker, result length: ' + result.length + ', resultCode:' + resultCode);
224    let want: Want = {
225      bundleName: BUNDLE_NAME,
226      flags: wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION,
227      parameters: {
228        'ability.params.stream': result
229      }
230    };
231    returnAbilityResult(want, resultCode, startModeOptions);
232  }
233
234  /**
235   * 文件创建完成,返回uri列表
236   * @param result
237   * @param resultCode
238   * @param message
239   */
240  export async function terminatePathPicker(result: string[],
241    resultCode: number = ResultCodePicker.SUCCESS, startModeOptions: StartModeOptions): Promise<void> {
242
243    Logger.i(TAG, 'enter terminatePathPicker, result length: ' + result.length + ', resultCode:' + resultCode);
244    let want: Want = {
245      bundleName: BUNDLE_NAME,
246      abilityName: ABILITY_LIST.PATH_PICKER,
247      flags: wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION,
248      parameters: {
249        'ability.params.stream': result,
250        KEY_PICK_SELECT_CLOUD_DISK: false
251      }
252    };
253    returnAbilityResult(want, resultCode, startModeOptions);
254  }
255
256
257
258  export function returnAbilityResult(want: Want, resultCode: number, options: StartModeOptions) {
259    Logger.i(TAG, 'returnPicker start');
260    if (options.windowType === PickerWindowType.ABILITY) {
261      let abilityResult: abilityResultInterface = {
262        want: want,
263        resultCode: resultCode
264      };
265      Logger.i(TAG, 'uiContext terminateSelfWithResult start');
266      options.uiContext.terminateSelfWithResult(abilityResult, (error) => {
267        Logger.i(TAG, 'terminateSelfWithResult is called = ' + error.code);
268      });
269    } else {
270      let abilityResult: ability.AbilityResult = {
271        resultCode: resultCode,
272        want: want
273      };
274      Logger.i(TAG, 'session terminateSelfWithResult start');
275      options.session.terminateSelfWithResult(abilityResult, (error) => {
276        Logger.e(TAG, 'closeUIExtFilePicker terminateSelfWithResult is called = ' + error?.code);
277      });
278    }
279  }
280
281  /**
282   * 获取选择文件的最大个数
283   * @param num 调用方传入的个数
284   */
285  export function getPickFileNum(num: any): number {
286    if (typeof num === 'number') {
287      if (num > 0 && num <= MAX_FILE_PICK_NUM) {
288        return num;
289      }
290    }
291    return MAX_FILE_PICK_NUM;
292  }
293
294  /**
295   * 获取选择文件的类型列表
296   * @param keyPickType 调用方传入文件类型(兼容双框架action)
297   * @param keyPickTypeList 调用方传入文件类型列表
298   */
299  export function getKeyPickTypeList(keyPickType, keyPickTypeList): Array<string> {
300    let typeList = []
301    if (keyPickType) {
302      typeList.push(keyPickType)
303    }
304    if (keyPickTypeList && keyPickTypeList.length !== 0) {
305      typeList = typeList.concat(keyPickTypeList)
306    }
307    return typeList.filter(item => item)
308  }
309
310  /**
311   * 获取选择文件Mode,默认选择文件
312   * @param keySelectMode 调用方传入文件mode
313   */
314  export function getKeySelectMode(keySelectMode: any): number {
315    if (typeof keySelectMode === 'number') {
316      if (keySelectMode === SELECT_MODE.FILE
317        || keySelectMode === SELECT_MODE.FOLDER
318        || keySelectMode === SELECT_MODE.MIX) {
319        return keySelectMode;
320      }
321    }
322    return SELECT_MODE.FILE;
323  }
324
325  /**
326   * 获取支持的文件后缀列表
327   * @param keyFileSuffixFilter 调用方传入文件后缀列表
328   */
329  export function getKeyFileSuffixFilter(keyFileSuffixFilter: string[]): Array<string> {
330    let suffixList = [];
331    if (!ArrayUtil.isEmpty(keyFileSuffixFilter)) {
332      let len = keyFileSuffixFilter.length;
333      let size = len > SUFFIX_LIST_MAX_LENGTH ? SUFFIX_LIST_MAX_LENGTH : len;
334      for (let index = 0; index < size; index++) {
335        const suffixStr = keyFileSuffixFilter[index];
336        if (typeof suffixStr === 'string') {
337          const suffixArray = suffixStr.split(FILE_SUFFIX.SUFFIX_SPLIT);
338          for (let index = 0; index < suffixArray.length; index++) {
339            const suffix = suffixArray[index];
340            if (checkFileSuffix(suffix)) {
341              suffixList.push(suffix.toUpperCase())
342            }
343          }
344        }
345      }
346    }
347    return suffixList.filter((item, index, array) => {
348      return array.indexOf(item) === index;
349    });
350  }
351
352  export function checkFileSuffix(fileSuffix: String): boolean {
353    return fileSuffix && fileSuffix.length <= SUFFIX_MAX_LENGTH && fileSuffix.startsWith(FILE_SUFFIX.SUFFIX_START);
354  }
355
356  /**
357   * 路径选择器获取支持的文件后缀,只支持获取第一个文件后缀
358   * @param keyFileSuffixChoices 调用方传入文件后缀列表
359   */
360  export function getKeyFileSuffixChoices(keyFileSuffixChoices: string[]): string {
361    if (!ArrayUtil.isEmpty(keyFileSuffixChoices)) {
362      let len = keyFileSuffixChoices.length;
363      let size = len > SUFFIX_LIST_MAX_LENGTH ? SUFFIX_LIST_MAX_LENGTH : len;
364      for (let index = 0; index < size; index++) {
365        const suffixStr = keyFileSuffixChoices[index];
366        if (typeof suffixStr === 'string') {
367          const suffixArray = suffixStr.split(FILE_SUFFIX.SUFFIX_SPLIT);
368          for (let index = 0; index < suffixArray.length; index++) {
369            const suffix = suffixArray[index];
370            if (checkFileSuffix(suffix)) {
371              return suffix;
372            }
373          }
374        }
375      }
376    }
377    return '';
378  }
379
380  /**
381   * 获取媒体库对象实例的统一接口
382   */
383  export function getPhotoManageHelper(): photoAccessHelper.PhotoAccessHelper {
384    if (!photoManageHelper) {
385      try {
386        photoManageHelper = photoAccessHelper.getPhotoAccessHelper(globalThis.abilityContext)
387      } catch (error) {
388        Logger.e(TAG, 'getPhotoManageHelper fail, error:' + JSON.stringify(error))
389      }
390    }
391    return photoManageHelper
392  }
393
394  export function releasePhotoManageHelper(): void {
395    if (!photoManageHelper) {
396      try {
397        photoManageHelper.release()
398      } catch (error) {
399        Logger.e(TAG, 'releasePhotoManageHelper fail, error: ' + JSON.stringify(error))
400      }
401    }
402  }
403}
404
405export default AbilityCommonUtil
406