• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 ErrorCodeConst from '../constants/ErrorCodeConst'
23import MediaLibrary from '@ohos.multimedia.mediaLibrary'
24import { FILE_MANAGER_PREFERENCES, FILE_SUFFIX, SELECT_MODE } from '../constants/Constant'
25import StringUtil from './StringUtil'
26import { ArrayUtil } from './ArrayUtil'
27import { getPreferences } from './PreferencesUtil'
28
29const TAG = 'AbilityCommonUtil'
30
31let mediaLibrary: MediaLibrary.MediaLibrary = null
32
33/**
34 * Ability公共工具类
35 */
36namespace AbilityCommonUtil {
37
38/**
39 * 需要用户授权的权限列表
40 */
41  export const PERMISSION_LIST: Array<Permissions> = [
42    "ohos.permission.MEDIA_LOCATION",
43    "ohos.permission.READ_MEDIA",
44    "ohos.permission.WRITE_MEDIA"
45  ]
46
47  /**
48   * 用来获取startAbility调用方应用uid的key
49   */
50  export const CALLER_UID = 'ohos.aafwk.param.callerUid'
51
52  export const CALLER_BUNDLE_NAME = 'ohos.aafwk.param.callerBundleName'
53
54  /**
55   * 最大选择文件的个数
56   */
57  export const MAX_FILE_PICK_NUM = 500
58
59  /**
60   * 后缀最大长度,包括'.'
61   */
62  export const SUFFIX_MAX_LENGTH: number = 255;
63
64  /**
65   * 三方传入的后缀数组长度最大100
66   */
67  export const SUFFIX_LIST_MAX_LENGTH: number = 100;
68
69  /**
70   * picker对外返回的响应码
71   */
72  export const RESULT_CODE = {
73    SUCCESS: 0,
74    CANCEL: -1
75  }
76
77  export const ABILITY_LIST = {
78    FILE_MANAGER: 'FileManagerAbility',
79    FILE_PICKER: 'FilePickerAbility',
80    PATH_PICKER: 'PathPickerAbility'
81  }
82  /**
83   * 拉起Ability时必要的初始化操作
84   */
85  export function init(): Promise<void[]> {
86    const fileAccessHelperPromise = createFileAccessHelper();
87    getMediaLibrary();
88    const getRequestPermission = requestPermission();
89    const initData = initLastSelectPath();
90    return Promise.all([fileAccessHelperPromise, getRequestPermission, initData]);
91  }
92
93  /**
94   * 获取FileAccessHelper,用于获取文件和操作文件
95   */
96  export function createFileAccessHelper(): Promise<void> {
97    if (globalThis.fileAccessHelper) {
98      return Promise.resolve()
99    }
100    return new Promise(async (resolve, reject) => {
101      try {
102        let wants = await fileAccess.getFileAccessAbilityInfo()
103        globalThis.fileAcsHelper = fileAccess.createFileAccessHelper(globalThis.abilityContext, wants)
104        // 获取设备根节点信息
105        const rootIterator: fileAccess.RootIterator = await globalThis.fileAcsHelper.getRoots()
106        let rootInfoArr = []
107        let result = rootIterator.next()
108        let isDone = result.done
109        while (!isDone) {
110          const rootInfo: fileAccess.RootInfo = result.value
111          Logger.i(TAG, 'RootInfo: ' + rootInfo.uri + ', ' + rootInfo.deviceType + ', ' + rootInfo.deviceFlags + ', ' + rootInfo.displayName+','+rootInfo.relativePath)
112          rootInfoArr.push(rootInfo)
113          result = rootIterator.next()
114          isDone = result.done
115        }
116        globalThis.rootInfoArr = rootInfoArr
117      } catch (err) {
118        Logger.e(TAG, 'createFileAccessHelper fail, error:' + JSON.stringify(err))
119      } finally {
120        resolve()
121      }
122    })
123  }
124
125  /**
126   * Ability初始化时,加载最新保存的路径Uri
127   */
128  export function initLastSelectPath(): Promise<void> {
129    return new Promise((resolve, reject) => {
130      const defaultValue = FILE_MANAGER_PREFERENCES.lastSelectPath.defaultValue;
131      const lastSelectPathKey = FILE_MANAGER_PREFERENCES.lastSelectPath.key;
132      getPreferences(FILE_MANAGER_PREFERENCES.name).then(preferences => {
133        preferences.get(lastSelectPathKey, defaultValue).then((result: string) => {
134          AppStorage.SetOrCreate<string>(lastSelectPathKey, result);
135          resolve();
136          Logger.i(TAG, 'initLastSelectPath result: ' + result);
137        }).catch((error) => {
138          AppStorage.SetOrCreate<string>(lastSelectPathKey, defaultValue);
139          Logger.e(TAG, 'initLastSelectPath preferences.get fail, error:' + JSON.stringify(error));
140          resolve();
141        })
142      }).catch(err => {
143        AppStorage.SetOrCreate<string>(lastSelectPathKey, defaultValue);
144        Logger.e(TAG, 'initLastSelectPath getPreferences fail, error: ' + JSON.stringify(err));
145        resolve();
146      })
147    })
148  }
149
150  /**
151   * 申请文件管理器需用户授权的权限
152   */
153  export function requestPermission(): Promise<void> {
154    let atManager = abilityAccessCtrl.createAtManager()
155    try {
156      return atManager.requestPermissionsFromUser(globalThis.abilityContext, PERMISSION_LIST).then((data) => {
157        if (data.authResults.some(item => item !== 0)) {
158          Logger.e(TAG, 'requestPermissionsFromUser some permission request fail, result:' + JSON.stringify(data))
159        } else {
160          Logger.i(TAG, 'requestPermissionsFromUser success, result:' + JSON.stringify(data))
161        }
162      }).catch((error) => {
163        Logger.e(TAG, 'requestPermissionsFromUser fail, error:' + JSON.stringify(error))
164      })
165    } catch (error) {
166      Logger.e(TAG, 'requestPermissionsFromUser error occurred, error:' + JSON.stringify(error))
167    }
168  }
169
170  /**
171   * uri授权
172   * @param uriList 待授权的uri列表
173   * @param bundleName 授权应用的包名
174   * @param flag 授权的权限,只读或读写
175   */
176  export function grantUriPermission(uriList: Array<string>, bundleName: string, flag: wantConstant.Flags): Promise<boolean> {
177    return new Promise(async (resolve, reject) => {
178      Logger.i(TAG, "grantUriPermission start,grantSize = " + uriList?.length);
179      let grantSuccessCount: number = 0;
180      for (let uri of uriList) {
181        try {
182          if (flag === wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION) {
183            await FileShare.grantUriPermission(uri, bundleName, flag | wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION)
184          } else {
185            await FileShare.grantUriPermission(uri, bundleName, flag)
186          }
187          grantSuccessCount++;
188        } catch (error) {
189          resolve(false)
190          Logger.e(TAG, `grantUriPermission fail,grantSuccessCount:${grantSuccessCount}}, uri: ${uri}, error: ${JSON.stringify(error)}`)
191          return
192        }
193      }
194      Logger.i(TAG, "grantUriPermission end,grantSuccessCount = " + grantSuccessCount);
195      resolve(true)
196    })
197
198  }
199
200  /**
201   * 文件选择完成,返回uri列表
202   * @param resultCode
203   * @param result
204   * @param message
205   */
206  export async function terminateFilePicker(result: Array<string> = [], displayNames: Array<string> = [], resultCode: number = RESULT_CODE.SUCCESS, message: string = ''): Promise<void> {
207    const bundleName = globalThis.pickerCallerBundleName
208    if (result.length && bundleName) {
209      // uri授权
210      const isSuccess = await grantUriPermission(result, bundleName, wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION)
211      if (!isSuccess) {
212        resultCode = ErrorCodeConst.PICKER.GRANT_URI_PERMISSION_FAIL,
213        result = []
214        message = 'uri grant permission fail'
215        displayNames = []
216      }
217    }
218
219    let abilityResult = {
220      resultCode: resultCode,
221      want: {
222        bundleName: globalThis.abilityContext.abilityInfo.bundleName,
223        abilityName: ABILITY_LIST.FILE_PICKER,
224        parameters: {
225          'select_item_list': result,
226          'file_name_list': displayNames,
227          message: message,
228          'result': result[0],
229        }
230      }
231    }
232    globalThis.abilityContext.terminateSelfWithResult(abilityResult, (error) => {
233      if (error.code) {
234        Logger.e(TAG, 'terminateFilePicker failed. Cause: ' + JSON.stringify(error))
235        return
236      }
237      Logger.d(TAG, 'terminateFilePicker success. result: ' + JSON.stringify(abilityResult))
238    })
239  }
240  /**
241   * 文件创建完成,返回uri列表
242   * @param result
243   * @param resultCode
244   * @param message
245   */
246  export async function terminatePathPicker(result: Array<string>, resultCode: number = RESULT_CODE.SUCCESS, message: string = ''): Promise<void> {
247    const bundleName = globalThis.pathCallerBundleName
248    if (result.length && bundleName) {
249      // uri授权
250      const flag = wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION
251      const isSuccess = await grantUriPermission(result, bundleName, flag)
252      if (!isSuccess) {
253        resultCode = ErrorCodeConst.PICKER.GRANT_URI_PERMISSION_FAIL,
254        result = []
255        message = 'uri grant permission fail'
256      }
257    }
258    let abilityResult = {
259      resultCode: resultCode,
260      want: {
261        bundleName: globalThis.pathAbilityContext.abilityInfo.bundleName,
262        abilityName: ABILITY_LIST.PATH_PICKER,
263        parameters: {
264          'pick_path_return': result,
265          'key_pick_select_clouddisk': false,
266          'message': message,
267          // 兼容老版本picker
268          'result': result[0]
269        }
270      }
271    }
272    globalThis.pathAbilityContext.terminateSelfWithResult(abilityResult, (error) => {
273      if (error.code) {
274        Logger.e(TAG, 'terminatePathPicker failed. Cause: ' + JSON.stringify(error))
275        return
276      }
277      Logger.d(TAG, 'terminatePathPicker success. result: ' + JSON.stringify(abilityResult))
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 getMediaLibrary(): MediaLibrary.MediaLibrary {
384    if (!mediaLibrary) {
385      try {
386        mediaLibrary = MediaLibrary.getMediaLibrary(globalThis.abilityContext)
387      } catch (error) {
388        Logger.e(TAG, 'getMediaLibrary fail, error:' + JSON.stringify(error))
389      }
390    }
391    return mediaLibrary
392  }
393
394  export function releaseMediaLibrary(): void {
395    if (!mediaLibrary) {
396      try {
397        mediaLibrary.release()
398      } catch (error) {
399        Logger.e(TAG, 'releaseMediaLibrary fail, error: ' + JSON.stringify(error))
400      }
401    }
402  }
403}
404
405export default AbilityCommonUtil
406