1/* 2 * Copyright (c) 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 16const photoAccessHelper = requireInternal('file.photoAccessHelper'); 17const bundleManager = requireNapi('bundle.bundleManager'); 18 19const ARGS_TWO = 2; 20 21const WRITE_PERMISSION = 'ohos.permission.WRITE_IMAGEVIDEO'; 22 23const PERMISSION_DENIED = 13900012; 24const ERR_CODE_PARAMERTER_INVALID = 13900020; 25const ERROR_MSG_WRITE_PERMISSION = 'not have ohos.permission.WRITE_IMAGEVIDEO'; 26const ERROR_MSG_USER_DENY = 'user deny'; 27const ERROR_MSG_PARAMERTER_INVALID = 'input parmaeter invalid'; 28 29const MAX_DELETE_NUMBER = 3600; 30const MIN_DELETE_NUMBER = 1; 31 32let gContext = undefined; 33 34class BusinessError extends Error { 35 constructor(msg, code) { 36 super(msg); 37 this.code = code || PERMISSION_DENIED; 38 } 39} 40function checkParams(uriList, asyncCallback) { 41 if (arguments.length > ARGS_TWO) { 42 return false; 43 } 44 if (!Array.isArray(uriList)) { 45 return false; 46 } 47 if (asyncCallback && typeof asyncCallback !== 'function') { 48 return false; 49 } 50 if (uriList.length < MIN_DELETE_NUMBER || uriList.length > MAX_DELETE_NUMBER) { 51 return false; 52 } 53 let tag = 'file://media/Photo/'; 54 for (let uri of uriList) { 55 if (!uri.includes(tag)) { 56 console.info(`photoAccessHelper invalid uri: ${uri}`); 57 return false; 58 } 59 } 60 return true; 61} 62function errorResult(rej, asyncCallback) { 63 if (asyncCallback) { 64 return asyncCallback(rej); 65 } 66 return new Promise((resolve, reject) => { 67 reject(rej); 68 }); 69} 70 71function getAbilityResource(bundleInfo) { 72 let labelId = bundleInfo.abilitiesInfo[0].labelId; 73 for (let abilityInfo of bundleInfo.abilitiesInfo) { 74 if (abilityInfo.name === bundleInfo.mainElementName) { 75 labelId = abilityInfo.labelId; 76 } 77 } 78 79 return labelId; 80} 81 82async function getAppName() { 83 let appName = ''; 84 try { 85 const flags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE; 86 const bundleInfo = await bundleManager.getBundleInfoForSelf(flags); 87 console.info(`photoAccessHelper bundleInfo: ${JSON.stringify(bundleInfo)}`) 88 if (bundleInfo === undefined || bundleInfo.hapModulesInfo === undefined || bundleInfo.hapModulesInfo.length === 0) { 89 return appName; 90 } 91 const labelId = getAbilityResource(bundleInfo.hapModulesInfo[0]); 92 const resourceMgr = gContext.resourceManager; 93 appName = await resourceMgr.getStringValue(labelId); 94 console.info(`photoAccessHelper appName: ${appName}`) 95 } catch (error) { 96 console.info(`photoAccessHelper error: ${JSON.stringify(error)}`) 97 } 98 99 return appName; 100} 101 102async function createPhotoDeleteRequestParamsOk(uriList, asyncCallback) { 103 let flags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION; 104 let { reqPermissionDetails } = await bundleManager.getBundleInfoForSelf(flags); 105 let isPermission = reqPermissionDetails.findIndex(({ name }) => name === WRITE_PERMISSION) !== -1; 106 if (!isPermission) { 107 return errorResult(new BusinessError(ERROR_MSG_WRITE_PERMISSION), asyncCallback); 108 } 109 const appName = await getAppName(); 110 if (appName.length === 0) { 111 console.info(`photoAccessHelper appName not found`) 112 } 113 const startParameter = { 114 action: 'ohos.want.action.deleteDialog', 115 type: 'image/*', 116 parameters: { 117 uris: uriList, 118 appName: appName 119 }, 120 }; 121 try { 122 const result = await gContext.requestDialogService(startParameter); 123 console.info(`photoAccessHelper result: ${JSON.stringify(result)}`); 124 if (result === null || result === undefined) { 125 console.log('photoAccessHelper createDeleteRequest return null'); 126 return errorResult(Error('requestDialog return undefined'), asyncCallback); 127 } 128 if (asyncCallback) { 129 if (result.result === 0) { 130 return asyncCallback(); 131 } else { 132 return asyncCallback(new BusinessError(ERROR_MSG_USER_DENY)); 133 } 134 } 135 return new Promise((resolve, reject) => { 136 if (result.result === 0) { 137 resolve(); 138 } else { 139 reject(new BusinessError(ERROR_MSG_USER_DENY)); 140 } 141 }); 142 } catch (error) { 143 return errorResult(new BusinessError(error.message, error.code), asyncCallback); 144 } 145} 146 147function createDeleteRequest(...params) { 148 if (!checkParams(...params)) { 149 throw new BusinessError(ERROR_MSG_PARAMERTER_INVALID, ERR_CODE_PARAMERTER_INVALID); 150 } 151 return createPhotoDeleteRequestParamsOk(...params); 152} 153 154function getPhotoAccessHelper(context) { 155 if (context === undefined) { 156 console.log('photoAccessHelper gContext undefined'); 157 throw Error('photoAccessHelper gContext undefined'); 158 } 159 gContext = context; 160 let helper = photoAccessHelper.getPhotoAccessHelper(gContext); 161 if (helper !== undefined) { 162 console.log('photoAccessHelper getPhotoAccessHelper inner add createDeleteRequest'); 163 helper.createDeleteRequest = createDeleteRequest; 164 } 165 return helper; 166} 167 168function getPhotoAccessHelperAsync(context, asyncCallback) { 169 if (context === undefined) { 170 console.log('photoAccessHelper gContext undefined'); 171 throw Error('photoAccessHelper gContext undefined'); 172 } 173 gContext = context; 174 if (arguments.length === 1) { 175 return photoAccessHelper.getPhotoAccessHelperAsync(gContext) 176 .then((helper) => { 177 if (helper !== undefined) { 178 console.log('photoAccessHelper getPhotoAccessHelperAsync inner add createDeleteRequest'); 179 helper.createDeleteRequest = createDeleteRequest; 180 } 181 return helper; 182 }) 183 .catch((err) => { 184 console.log('photoAccessHelper getPhotoAccessHelperAsync err ' + err); 185 throw Error(err); 186 }); 187 } else if (arguments.length === ARGS_TWO && typeof asyncCallback === 'function') { 188 photoAccessHelper.getPhotoAccessHelperAsync(gContext, (err, helper) => { 189 console.log('photoAccessHelper getPhotoAccessHelperAsync callback ' + err); 190 if (err) { 191 asyncCallback(err); 192 } else { 193 if (helper !== undefined) { 194 console.log('photoAccessHelper getPhotoAccessHelperAsync callback add createDeleteRequest'); 195 helper.createDeleteRequest = createDeleteRequest; 196 } 197 asyncCallback(err, helper); 198 } 199 }); 200 } else { 201 console.log('photoAccessHelper getPhotoAccessHelperAsync param invalid'); 202 throw new BusinessError(ERROR_MSG_PARAMERTER_INVALID, ERR_CODE_PARAMERTER_INVALID); 203 } 204 return undefined; 205} 206 207const PhotoViewMIMETypes = { 208 IMAGE_TYPE: 'image/*', 209 VIDEO_TYPE: 'video/*', 210 IMAGE_VIDEO_TYPE: '*/*', 211 INVALID_TYPE: '' 212} 213 214const ErrCode = { 215 INVALID_ARGS: 13900020, 216 RESULT_ERROR: 13900042, 217} 218 219const ERRCODE_MAP = new Map([ 220 [ErrCode.INVALID_ARGS, 'Invalid argument'], 221 [ErrCode.RESULT_ERROR, 'Unknown error'], 222]); 223 224const PHOTO_VIEW_MIME_TYPE_MAP = new Map([ 225 [PhotoViewMIMETypes.IMAGE_TYPE, 'FILTER_MEDIA_TYPE_IMAGE'], 226 [PhotoViewMIMETypes.VIDEO_TYPE, 'FILTER_MEDIA_TYPE_VIDEO'], 227 [PhotoViewMIMETypes.IMAGE_VIDEO_TYPE, 'FILTER_MEDIA_TYPE_ALL'], 228]); 229 230const ARGS_ZERO = 0; 231const ARGS_ONE = 1; 232 233function checkArguments(args) { 234 let checkArgumentsResult = undefined; 235 236 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] !== 'function') { 237 checkArgumentsResult = getErr(ErrCode.INVALID_ARGS); 238 } 239 240 if (args.length > 0 && typeof args[ARGS_ZERO] === 'object') { 241 let option = args[ARGS_ZERO]; 242 if (option.maxSelectNumber !== undefined) { 243 if (option.maxSelectNumber.toString().indexOf('.') !== -1) { 244 checkArgumentsResult = getErr(ErrCode.INVALID_ARGS); 245 } 246 } 247 } 248 249 return checkArgumentsResult; 250} 251 252function getErr(errCode) { 253 return { code: errCode, message: ERRCODE_MAP.get(errCode) }; 254} 255 256function parsePhotoPickerSelectOption(args) { 257 let config = { 258 action: 'ohos.want.action.photoPicker', 259 type: 'multipleselect', 260 parameters: { 261 uri: 'multipleselect', 262 }, 263 }; 264 265 if (args.length > ARGS_ZERO && typeof args[ARGS_ZERO] === 'object') { 266 let option = args[ARGS_ZERO]; 267 if (option.maxSelectNumber && option.maxSelectNumber > 0) { 268 let select = (option.maxSelectNumber === 1) ? 'singleselect' : 'multipleselect'; 269 config.type = select; 270 config.parameters.uri = select; 271 config.parameters.maxSelectCount = option.maxSelectNumber; 272 } 273 if (option.MIMEType && PHOTO_VIEW_MIME_TYPE_MAP.has(option.MIMEType)) { 274 config.parameters.filterMediaType = PHOTO_VIEW_MIME_TYPE_MAP.get(option.MIMEType); 275 } 276 } 277 278 return config; 279} 280 281function getPhotoPickerSelectResult(args) { 282 let selectResult = { 283 error: undefined, 284 data: undefined, 285 }; 286 287 if (args.resultCode === 0) { 288 if (args.want && args.want.parameters) { 289 let uris = args.want.parameters['select-item-list']; 290 let isOrigin = args.want.parameters.isOriginal; 291 selectResult.data = new PhotoSelectResult(uris, isOrigin); 292 } 293 } else if (result.resultCode === -1) { 294 selectResult.data = new PhotoSelectResult([], undefined); 295 } else { 296 selectResult.error = getErr(ErrCode.RESULT_ERROR); 297 } 298 299 return selectResult; 300} 301 302async function photoPickerSelect(...args) { 303 let checkArgsResult = checkArguments(args); 304 if (checkArgsResult !== undefined) { 305 console.log('[picker] Invalid argument'); 306 throw checkArgsResult; 307 } 308 309 const config = parsePhotoPickerSelectOption(args); 310 console.log('[picker] config: ' + JSON.stringify(config)); 311 312 try { 313 let context = getContext(this); 314 let result = await context.startAbilityForResult(config, {windowMode: 1}); 315 console.log('[picker] result: ' + JSON.stringify(result)); 316 const selectResult = getPhotoPickerSelectResult(result); 317 console.log('[picker] selectResult: ' + JSON.stringify(selectResult)); 318 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] === 'function') { 319 return args[ARGS_ONE](selectResult.error, selectResult.data); 320 } else if (args.length === ARGS_ONE && typeof args[ARGS_ZERO] === 'function') { 321 return args[ARGS_ZERO](selectResult.error, selectResult.data); 322 } 323 return new Promise((resolve, reject) => { 324 if (selectResult.data !== undefined) { 325 resolve(selectResult.data); 326 } else { 327 reject(selectResult.error); 328 } 329 }) 330 } catch (error) { 331 console.log('[picker] error: ' + error); 332 } 333 return undefined; 334} 335 336function PhotoSelectOptions() { 337 this.MIMEType = PhotoViewMIMETypes.INVALID_TYPE; 338 this.maxSelectNumber = -1; 339} 340 341function PhotoSelectResult(uris, isOriginalPhoto) { 342 this.photoUris = uris; 343 this.isOriginalPhoto = isOriginalPhoto; 344} 345 346function PhotoViewPicker() { 347 this.select = photoPickerSelect; 348} 349 350export default { 351 getPhotoAccessHelper, 352 getPhotoAccessHelperAsync, 353 PhotoType: photoAccessHelper.PhotoType, 354 PhotoKeys: photoAccessHelper.PhotoKeys, 355 AlbumKeys: photoAccessHelper.AlbumKeys, 356 AlbumType: photoAccessHelper.AlbumType, 357 AlbumSubtype: photoAccessHelper.AlbumSubtype, 358 PositionType: photoAccessHelper.PositionType, 359 PhotoSubtype: photoAccessHelper.PhotoSubtype, 360 NotifyType: photoAccessHelper.NotifyType, 361 DefaultChangeUri: photoAccessHelper.DefaultChangeUri, 362 PhotoViewMIMETypes: PhotoViewMIMETypes, 363 PhotoSelectOptions: PhotoSelectOptions, 364 PhotoSelectResult: PhotoSelectResult, 365 PhotoViewPicker: PhotoViewPicker, 366}; 367