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