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