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; 20const ARGS_THREE = 3; 21 22const WRITE_PERMISSION = 'ohos.permission.WRITE_IMAGEVIDEO'; 23 24const PERMISSION_DENIED = 13900012; 25const ERR_CODE_PARAMERTER_INVALID = 13900020; 26const ERR_CODE_OHOS_PERMISSION_DENIED = 201; 27const ERR_CODE_OHOS_PARAMERTER_INVALID = 401; 28const REQUEST_CODE_SUCCESS = 0; 29const PERMISSION_STATE_ERROR = -1; 30const ERROR_MSG_WRITE_PERMISSION = 'not have ohos.permission.WRITE_IMAGEVIDEO'; 31const ERROR_MSG_USER_DENY = 'user deny'; 32const ERROR_MSG_PARAMERTER_INVALID = 'input parmaeter invalid'; 33const ERROR_MSG_INNER_FAIL = 'System inner fail'; 34 35const MAX_DELETE_NUMBER = 300; 36const MIN_DELETE_NUMBER = 1; 37 38let gContext = undefined; 39 40class BusinessError extends Error { 41 constructor(msg, code) { 42 super(msg); 43 this.code = code || PERMISSION_DENIED; 44 } 45} 46function checkParams(uriList, asyncCallback) { 47 if (arguments.length > ARGS_TWO) { 48 return false; 49 } 50 if (!Array.isArray(uriList)) { 51 return false; 52 } 53 if (asyncCallback && typeof asyncCallback !== 'function') { 54 return false; 55 } 56 if (uriList.length < MIN_DELETE_NUMBER || uriList.length > MAX_DELETE_NUMBER) { 57 return false; 58 } 59 let tag = 'file://media/Photo/'; 60 for (let uri of uriList) { 61 if (!uri.includes(tag)) { 62 console.info(`photoAccessHelper invalid uri: ${uri}`); 63 return false; 64 } 65 } 66 return true; 67} 68function errorResult(rej, asyncCallback) { 69 if (asyncCallback) { 70 return asyncCallback(rej); 71 } 72 return new Promise((resolve, reject) => { 73 reject(rej); 74 }); 75} 76 77function getAbilityResource(bundleInfo) { 78 let labelId = bundleInfo.abilitiesInfo[0].labelId; 79 for (let abilityInfo of bundleInfo.abilitiesInfo) { 80 if (abilityInfo.name === bundleInfo.mainElementName) { 81 labelId = abilityInfo.labelId; 82 } 83 } 84 85 return labelId; 86} 87 88async function getAppName() { 89 let appName = ''; 90 try { 91 const flags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE; 92 const bundleInfo = await bundleManager.getBundleInfoForSelf(flags); 93 console.info(`photoAccessHelper bundleInfo: ${JSON.stringify(bundleInfo)}`) 94 if (bundleInfo === undefined || bundleInfo.hapModulesInfo === undefined || bundleInfo.hapModulesInfo.length === 0) { 95 return appName; 96 } 97 const labelId = getAbilityResource(bundleInfo.hapModulesInfo[0]); 98 const resourceMgr = gContext.resourceManager; 99 appName = await resourceMgr.getStringValue(labelId); 100 console.info(`photoAccessHelper appName: ${appName}`) 101 } catch (error) { 102 console.info(`photoAccessHelper error: ${JSON.stringify(error)}`) 103 } 104 105 return appName; 106} 107 108async function createPhotoDeleteRequestParamsOk(uriList, asyncCallback) { 109 let flags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION; 110 let { reqPermissionDetails, permissionGrantStates } = await bundleManager.getBundleInfoForSelf(flags); 111 let permissionIndex = -1; 112 for (let i = 0; i < reqPermissionDetails.length; i++) { 113 if (reqPermissionDetails[i].name === WRITE_PERMISSION) { 114 permissionIndex = i; 115 } 116 } 117 if (permissionIndex < 0 || permissionGrantStates[permissionIndex] === PERMISSION_STATE_ERROR) { 118 console.info('photoAccessHelper permission error'); 119 return errorResult(new BusinessError(ERROR_MSG_WRITE_PERMISSION), asyncCallback); 120 } 121 const appName = await getAppName(); 122 if (appName.length === 0) { 123 console.info(`photoAccessHelper appName not found`); 124 return errorResult(new BusinessError(ERROR_MSG_PARAMERTER_INVALID, ERR_CODE_PARAMERTER_INVALID), asyncCallback); 125 } 126 try { 127 if (asyncCallback) { 128 return photoAccessHelper.createDeleteRequest(getContext(this), appName, uriList, result => { 129 if (result.result === REQUEST_CODE_SUCCESS) { 130 asyncCallback(); 131 } else if (result.result == PERMISSION_DENIED) { 132 asyncCallback(new BusinessError(ERROR_MSG_USER_DENY)); 133 } else { 134 asyncCallback(new BusinessError(ERROR_MSG_INNER_FAIL, result.result)); 135 } 136 }); 137 } else { 138 return new Promise((resolve, reject) => { 139 photoAccessHelper.createDeleteRequest(getContext(this), appName, uriList, result => { 140 if (result.result === REQUEST_CODE_SUCCESS) { 141 resolve(); 142 } else if (result.result == PERMISSION_DENIED) { 143 reject(new BusinessError(ERROR_MSG_USER_DENY)); 144 } else { 145 reject(new BusinessError(ERROR_MSG_INNER_FAIL, result.result)); 146 } 147 }); 148 }); 149 } 150 } catch (error) { 151 return errorResult(new BusinessError(error.message, error.code), asyncCallback); 152 } 153} 154 155function createDeleteRequest(...params) { 156 if (!checkParams(...params)) { 157 throw new BusinessError(ERROR_MSG_PARAMERTER_INVALID, ERR_CODE_PARAMERTER_INVALID); 158 } 159 return createPhotoDeleteRequestParamsOk(...params); 160} 161 162function getPhotoAccessHelper(context) { 163 if (context === undefined) { 164 console.log('photoAccessHelper gContext undefined'); 165 throw Error('photoAccessHelper gContext undefined'); 166 } 167 gContext = context; 168 let helper = photoAccessHelper.getPhotoAccessHelper(gContext); 169 if (helper !== undefined) { 170 console.log('photoAccessHelper getPhotoAccessHelper inner add createDeleteRequest'); 171 helper.createDeleteRequest = createDeleteRequest; 172 } 173 return helper; 174} 175 176function startPhotoPicker(context, config) { 177 if (context === undefined) { 178 console.log('photoAccessHelper gContext undefined'); 179 throw Error('photoAccessHelper gContext undefined'); 180 } 181 if (config === undefined) { 182 console.log('photoAccessHelper config undefined'); 183 throw Error('photoAccessHelper config undefined'); 184 } 185 gContext = context; 186 let helper = photoAccessHelper.startPhotoPicker(gContext, config); 187 if (helper !== undefined) { 188 console.log('photoAccessHelper startPhotoPicker inner add createDeleteRequest'); 189 helper.createDeleteRequest = createDeleteRequest; 190 } 191 return helper; 192} 193 194function getPhotoAccessHelperAsync(context, asyncCallback) { 195 if (context === undefined) { 196 console.log('photoAccessHelper gContext undefined'); 197 throw Error('photoAccessHelper gContext undefined'); 198 } 199 gContext = context; 200 if (arguments.length === 1) { 201 return photoAccessHelper.getPhotoAccessHelperAsync(gContext) 202 .then((helper) => { 203 if (helper !== undefined) { 204 console.log('photoAccessHelper getPhotoAccessHelperAsync inner add createDeleteRequest'); 205 helper.createDeleteRequest = createDeleteRequest; 206 } 207 return helper; 208 }) 209 .catch((err) => { 210 console.log('photoAccessHelper getPhotoAccessHelperAsync err ' + err); 211 throw Error(err); 212 }); 213 } else if (arguments.length === ARGS_TWO && typeof asyncCallback === 'function') { 214 photoAccessHelper.getPhotoAccessHelperAsync(gContext, (err, helper) => { 215 console.log('photoAccessHelper getPhotoAccessHelperAsync callback ' + err); 216 if (err) { 217 asyncCallback(err); 218 } else { 219 if (helper !== undefined) { 220 console.log('photoAccessHelper getPhotoAccessHelperAsync callback add createDeleteRequest'); 221 helper.createDeleteRequest = createDeleteRequest; 222 } 223 asyncCallback(err, helper); 224 } 225 }); 226 } else { 227 console.log('photoAccessHelper getPhotoAccessHelperAsync param invalid'); 228 throw new BusinessError(ERROR_MSG_PARAMERTER_INVALID, ERR_CODE_PARAMERTER_INVALID); 229 } 230 return undefined; 231} 232 233const RecommendationType = { 234 // Indicates that QR code or barcode photos can be recommended 235 QR_OR_BAR_CODE: 1, 236 237 // Indicates that QR code photos can be recommended 238 QR_CODE: 2, 239 240 // Indicates that barcode photos can be recommended 241 BAR_CODE: 3, 242 243 // Indicates that QR code or barcode photos can be recommended 244 ID_CARD: 4, 245 246 // Indicates that profile picture photos can be recommended 247 PROFILE_PICTURE: 5 248} 249 250const PhotoViewMIMETypes = { 251 IMAGE_TYPE: 'image/*', 252 VIDEO_TYPE: 'video/*', 253 IMAGE_VIDEO_TYPE: '*/*', 254 INVALID_TYPE: '' 255} 256 257const ErrCode = { 258 INVALID_ARGS: 13900020, 259 RESULT_ERROR: 13900042, 260 CONTEXT_NO_EXIST: 16000011, 261} 262 263const ERRCODE_MAP = new Map([ 264 [ErrCode.INVALID_ARGS, 'Invalid argument'], 265 [ErrCode.RESULT_ERROR, 'Unknown error'], 266 [ErrCode.CONTEXT_NO_EXIST, 'Current ability failed to obtain context'], 267]); 268 269const PHOTO_VIEW_MIME_TYPE_MAP = new Map([ 270 [PhotoViewMIMETypes.IMAGE_TYPE, 'FILTER_MEDIA_TYPE_IMAGE'], 271 [PhotoViewMIMETypes.VIDEO_TYPE, 'FILTER_MEDIA_TYPE_VIDEO'], 272 [PhotoViewMIMETypes.IMAGE_VIDEO_TYPE, 'FILTER_MEDIA_TYPE_ALL'], 273]); 274 275const ARGS_ZERO = 0; 276const ARGS_ONE = 1; 277 278function checkArguments(args) { 279 let checkArgumentsResult = undefined; 280 281 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] !== 'function') { 282 checkArgumentsResult = getErr(ErrCode.INVALID_ARGS); 283 } 284 285 if (args.length > 0 && typeof args[ARGS_ZERO] === 'object') { 286 let option = args[ARGS_ZERO]; 287 if (option.maxSelectNumber !== undefined) { 288 if (option.maxSelectNumber.toString().indexOf('.') !== -1) { 289 checkArgumentsResult = getErr(ErrCode.INVALID_ARGS); 290 } 291 } 292 } 293 294 return checkArgumentsResult; 295} 296 297function getErr(errCode) { 298 return { code: errCode, message: ERRCODE_MAP.get(errCode) }; 299} 300 301function parsePhotoPickerSelectOption(args) { 302 let config = { 303 action: 'ohos.want.action.photoPicker', 304 type: 'multipleselect', 305 parameters: { 306 uri: 'multipleselect', 307 }, 308 }; 309 310 if (args.length > ARGS_ZERO && typeof args[ARGS_ZERO] === 'object') { 311 let option = args[ARGS_ZERO]; 312 if (option.maxSelectNumber && option.maxSelectNumber > 0) { 313 let select = (option.maxSelectNumber === 1) ? 'singleselect' : 'multipleselect'; 314 config.type = select; 315 config.parameters.uri = select; 316 config.parameters.maxSelectCount = option.maxSelectNumber; 317 } 318 if (option.MIMEType && PHOTO_VIEW_MIME_TYPE_MAP.has(option.MIMEType)) { 319 config.parameters.filterMediaType = PHOTO_VIEW_MIME_TYPE_MAP.get(option.MIMEType); 320 } 321 config.parameters.isSearchSupported = option.isSearchSupported === undefined || option.isSearchSupported; 322 config.parameters.isPhotoTakingSupported = option.isPhotoTakingSupported === undefined || option.isPhotoTakingSupported; 323 config.parameters.isEditSupported = option.isEditSupported === undefined || option.isEditSupported; 324 config.parameters.recommendationOptions = option.recommendationOptions; 325 config.parameters.preselectedUris = option.preselectedUris; 326 } 327 328 return config; 329} 330 331function getPhotoPickerSelectResult(args) { 332 let selectResult = { 333 error: undefined, 334 data: undefined, 335 }; 336 337 if (args.resultCode === 0) { 338 let uris = args.uris; 339 let isOrigin = args.isOrigin; 340 selectResult.data = new PhotoSelectResult(uris, isOrigin); 341 } else if (args.resultCode === -1) { 342 selectResult.data = new PhotoSelectResult([], undefined); 343 } else { 344 selectResult.error = getErr(ErrCode.RESULT_ERROR); 345 } 346 347 return selectResult; 348} 349 350async function photoPickerSelect(...args) { 351 let checkArgsResult = checkArguments(args); 352 if (checkArgsResult !== undefined) { 353 console.log('[picker] Invalid argument'); 354 throw checkArgsResult; 355 } 356 357 const config = parsePhotoPickerSelectOption(args); 358 console.log('[picker] config: ' + JSON.stringify(config)); 359 360 let context = undefined; 361 try { 362 context = getContext(this); 363 } catch (getContextError) { 364 console.error('[picker] getContext error: ' + getContextError); 365 throw getErr(ErrCode.CONTEXT_NO_EXIST); 366 } 367 try { 368 if (context === undefined) { 369 throw getErr(ErrCode.CONTEXT_NO_EXIST); 370 } 371 let result = await startPhotoPicker(context, config); 372 console.log('[picker] result: ' + JSON.stringify(result)); 373 const selectResult = getPhotoPickerSelectResult(result); 374 console.log('[picker] selectResult: ' + JSON.stringify(selectResult)); 375 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] === 'function') { 376 return args[ARGS_ONE](selectResult.error, selectResult.data); 377 } else if (args.length === ARGS_ONE && typeof args[ARGS_ZERO] === 'function') { 378 return args[ARGS_ZERO](selectResult.error, selectResult.data); 379 } 380 return new Promise((resolve, reject) => { 381 if (selectResult.data !== undefined) { 382 resolve(selectResult.data); 383 } else { 384 reject(selectResult.error); 385 } 386 }) 387 } catch (error) { 388 console.error('[picker] error: ' + JSON.stringify(error)); 389 } 390 return undefined; 391} 392 393function PhotoSelectOptions() { 394 this.MIMEType = PhotoViewMIMETypes.INVALID_TYPE; 395 this.maxSelectNumber = -1; 396 this.isSearchSupported = true; 397 this.isPhotoTakingSupported = true; 398 this.isEditSupported = true; 399} 400 401function PhotoSelectResult(uris, isOriginalPhoto) { 402 this.photoUris = uris; 403 this.isOriginalPhoto = isOriginalPhoto; 404} 405 406function PhotoViewPicker() { 407 this.select = photoPickerSelect; 408} 409 410function RecommendationOptions() { 411} 412 413class MediaAssetChangeRequest extends photoAccessHelper.MediaAssetChangeRequest { 414 static deleteAssets(context, assets, asyncCallback) { 415 if (arguments.length > ARGS_THREE || arguments.length < ARGS_TWO) { 416 throw new BusinessError(ERROR_MSG_PARAMERTER_INVALID, ERR_CODE_OHOS_PARAMERTER_INVALID); 417 } 418 419 try { 420 if (asyncCallback) { 421 return super.deleteAssets(context, result => { 422 if (result.result === REQUEST_CODE_SUCCESS) { 423 asyncCallback(); 424 } else if (result.result === PERMISSION_DENIED) { 425 asyncCallback(new BusinessError(ERROR_MSG_USER_DENY, ERR_CODE_OHOS_PERMISSION_DENIED)); 426 } else { 427 asyncCallback(new BusinessError(ERROR_MSG_INNER_FAIL, result.result)); 428 } 429 }, assets, asyncCallback); 430 } 431 432 return new Promise((resolve, reject) => { 433 super.deleteAssets(context, result => { 434 if (result.result === REQUEST_CODE_SUCCESS) { 435 resolve(); 436 } else if (result.result === PERMISSION_DENIED) { 437 reject(new BusinessError(ERROR_MSG_USER_DENY, ERR_CODE_OHOS_PERMISSION_DENIED)); 438 } else { 439 reject(new BusinessError(ERROR_MSG_INNER_FAIL, result.result)); 440 } 441 }, assets, (err) => { 442 if (err) { 443 reject(err); 444 } else { 445 resolve(); 446 } 447 }); 448 }); 449 } catch (error) { 450 return errorResult(new BusinessError(error.message, error.code), asyncCallback); 451 } 452 } 453} 454 455export default { 456 getPhotoAccessHelper, 457 startPhotoPicker, 458 getPhotoAccessHelperAsync, 459 PhotoType: photoAccessHelper.PhotoType, 460 PhotoKeys: photoAccessHelper.PhotoKeys, 461 AlbumKeys: photoAccessHelper.AlbumKeys, 462 AlbumType: photoAccessHelper.AlbumType, 463 AlbumSubtype: photoAccessHelper.AlbumSubtype, 464 PositionType: photoAccessHelper.PositionType, 465 PhotoSubtype: photoAccessHelper.PhotoSubtype, 466 NotifyType: photoAccessHelper.NotifyType, 467 DefaultChangeUri: photoAccessHelper.DefaultChangeUri, 468 HiddenPhotosDisplayMode: photoAccessHelper.HiddenPhotosDisplayMode, 469 AnalysisType: photoAccessHelper.AnalysisType, 470 RequestPhotoType: photoAccessHelper.RequestPhotoType, 471 PhotoViewMIMETypes: PhotoViewMIMETypes, 472 DeliveryMode: photoAccessHelper.DeliveryMode, 473 SourceMode: photoAccessHelper.SourceMode, 474 PhotoSelectOptions: PhotoSelectOptions, 475 PhotoSelectResult: PhotoSelectResult, 476 PhotoViewPicker: PhotoViewPicker, 477 RecommendationType: RecommendationType, 478 RecommendationOptions: RecommendationOptions, 479 ResourceType: photoAccessHelper.ResourceType, 480 MediaAssetEditData: photoAccessHelper.MediaAssetEditData, 481 MediaAssetChangeRequest: MediaAssetChangeRequest, 482 MediaAssetsChangeRequest: photoAccessHelper.MediaAssetsChangeRequest, 483 MediaAlbumChangeRequest: photoAccessHelper.MediaAlbumChangeRequest, 484 MediaAssetManager: photoAccessHelper.MediaAssetManager, 485}; 486