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 PhotoViewMIMETypes = { 17 IMAGE_TYPE: 'image/*', 18 VIDEO_TYPE: 'video/*', 19 IMAGE_VIDEO_TYPE: '*/*', 20 INVALID_TYPE: '' 21} 22 23const ErrCode = { 24 INVALID_ARGS: 13900020, 25 RESULT_ERROR: 13900042, 26 NAME_TOO_LONG: 13900030, 27} 28 29const ERRCODE_MAP = new Map([ 30 [ErrCode.INVALID_ARGS, 'Invalid argument'], 31 [ErrCode.RESULT_ERROR, 'Unknown error'], 32 [ErrCode.NAME_TOO_LONG, 'File name too long'], 33]); 34 35const PHOTO_VIEW_MIME_TYPE_MAP = new Map([ 36 [PhotoViewMIMETypes.IMAGE_TYPE, 'FILTER_MEDIA_TYPE_IMAGE'], 37 [PhotoViewMIMETypes.VIDEO_TYPE, 'FILTER_MEDIA_TYPE_VIDEO'], 38 [PhotoViewMIMETypes.IMAGE_VIDEO_TYPE, 'FILTER_MEDIA_TYPE_ALL'], 39]); 40 41const ACTION = { 42 SELECT_ACTION: 'ohos.want.action.OPEN_FILE', 43 SELECT_ACTION_MODAL: 'ohos.want.action.OPEN_FILE_SERVICE', 44 SAVE_ACTION: 'ohos.want.action.CREATE_FILE', 45 SAVE_ACTION_MODAL: 'ohos.want.action.CREATE_FILE_SERVICE', 46} 47 48const CREATE_FILE_NAME_LENGTH_LIMIT = 256; 49const ARGS_ZERO = 0; 50const ARGS_ONE = 1; 51const ARGS_TWO = 2; 52 53/* 54* UTF-8字符编码数值对应的存储长度: 55* 0000 - 0x007F (eg: a~z A~Z 0~9) 56* 0080 - 0x07FF (eg: 希腊字母) 57* 0800 - 0xFFFF (eg: 中文) 58* 其他 (eg: 平面符号) 59*/ 60function strSizeUTF8(str) { 61 let strLen = str.length; 62 let bytesLen = 0; 63 for (let i = 0; i < strLen; i++) { 64 let charCode = str.charCodeAt(i); 65 if (charCode <= 0x007f) { 66 bytesLen++; 67 } else if (charCode <= 0x07ff) { 68 bytesLen += 2; 69 } else if (charCode <= 0xffff) { 70 bytesLen += 3; 71 } else { 72 bytesLen += 4; 73 } 74 } 75 return bytesLen; 76} 77 78function checkArguments(args) { 79 let checkArgumentsResult = undefined; 80 81 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] !== 'function') { 82 checkArgumentsResult = getErr(ErrCode.INVALID_ARGS); 83 } 84 85 if (args.length > 0 && typeof args[ARGS_ZERO] === 'object') { 86 let option = args[ARGS_ZERO]; 87 if (option.maxSelectNumber !== undefined) { 88 if (option.maxSelectNumber.toString().indexOf('.') !== -1) { 89 checkArgumentsResult = getErr(ErrCode.INVALID_ARGS); 90 } 91 } 92 93 if (option.newFileNames !== undefined && option.newFileNames.length > 0) { 94 for (let i = 0; i < option.newFileNames.length; i++) { 95 let value = option.newFileNames[i]; 96 if (strSizeUTF8(value) >= CREATE_FILE_NAME_LENGTH_LIMIT) { 97 console.log('[picker] checkArguments Invalid name: ' + value); 98 checkArgumentsResult = getErr(ErrCode.NAME_TOO_LONG); 99 } 100 } 101 } 102 } 103 104 return checkArgumentsResult; 105} 106 107function getErr(errCode) { 108 return {code: errCode, message: ERRCODE_MAP.get(errCode)}; 109} 110 111function parsePhotoPickerSelectOption(args) { 112 let config = { 113 action: 'ohos.want.action.photoPicker', 114 type: 'multipleselect', 115 parameters: { 116 uri: 'multipleselect', 117 }, 118 }; 119 120 if (args.length > ARGS_ZERO && typeof args[ARGS_ZERO] === 'object') { 121 let option = args[ARGS_ZERO]; 122 if (option.maxSelectNumber && option.maxSelectNumber > 0) { 123 let select = (option.maxSelectNumber === 1) ? 'singleselect' : 'multipleselect'; 124 config.type = select; 125 config.parameters.uri = select; 126 config.parameters.maxSelectCount = option.maxSelectNumber; 127 } 128 if (option.MIMEType && PHOTO_VIEW_MIME_TYPE_MAP.has(option.MIMEType)) { 129 config.parameters.filterMediaType = PHOTO_VIEW_MIME_TYPE_MAP.get(option.MIMEType); 130 } 131 } 132 133 return config; 134} 135 136function getPhotoPickerSelectResult(args) { 137 let selectResult = { 138 error: undefined, 139 data: undefined, 140 }; 141 142 if (args.resultCode === 0) { 143 if (args.want && args.want.parameters) { 144 let uris = args.want.parameters['select-item-list']; 145 let isOrigin = args.want.parameters.isOriginal; 146 selectResult.data = new PhotoSelectResult(uris, isOrigin); 147 } 148 } else if (result.resultCode === -1) { 149 selectResult.data = new PhotoSelectResult([], undefined); 150 } else { 151 selectResult.error = getErr(ErrCode.RESULT_ERROR); 152 } 153 154 return selectResult; 155} 156 157async function photoPickerSelect(...args) { 158 let checkArgsResult = checkArguments(args); 159 if (checkArgsResult !== undefined) { 160 console.log('[picker] Invalid argument'); 161 throw checkArgsResult; 162 } 163 164 const config = parsePhotoPickerSelectOption(args); 165 console.log('[picker] config: ' + JSON.stringify(config)); 166 167 try { 168 let context = getContext(this); 169 let result = await context.startAbilityForResult(config, {windowMode: 1}); 170 console.log('[picker] result: ' + JSON.stringify(result)); 171 const selectResult = getPhotoPickerSelectResult(result); 172 console.log('[picker] selectResult: ' + JSON.stringify(selectResult)); 173 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] === 'function') { 174 return args[ARGS_ONE](selectResult.error, selectResult.data); 175 } else if (args.length === ARGS_ONE && typeof args[ARGS_ZERO] === 'function') { 176 return args[ARGS_ZERO](selectResult.error, selectResult.data); 177 } 178 return new Promise((resolve, reject) => { 179 if (selectResult.data !== undefined) { 180 resolve(selectResult.data); 181 } else { 182 reject(selectResult.error); 183 } 184 }) 185 } catch (error) { 186 console.log('[picker] error: ' + error); 187 } 188 return undefined; 189} 190 191function parseDocumentPickerSelectOption(args, action) { 192 let config = { 193 action: action, 194 parameters: { 195 startMode: 'choose', 196 } 197 }; 198 199 if (args.length > ARGS_ZERO && typeof args[ARGS_ZERO] === 'object') { 200 let option = args[ARGS_ZERO]; 201 202 if ((option.maxSelectNumber !== undefined) && option.maxSelectNumber > 0) { 203 config.parameters.key_pick_num = option.maxSelectNumber; 204 } 205 if (option.defaultFilePathUri !== undefined) { 206 config.parameters.key_pick_dir_path = option.defaultFilePathUri; 207 } 208 if ((option.fileSuffixFilters !== undefined) && option.fileSuffixFilters.length > 0) { 209 config.parameters.key_file_suffix_filter = option.fileSuffixFilters; 210 } 211 } 212 213 console.log('[picker] Select config: ' + JSON.stringify(config)); 214 return config; 215} 216 217function getDocumentPickerSelectResult(args) { 218 let selectResult = { 219 error: undefined, 220 data: undefined 221 }; 222 // 0:success 223 // -1:Non modal cancel 224 // 1:Modal cancel 225 // ResultCode is a non modal return code. 226 // Result is the return code of the modality. 227 if ((args.resultCode !== undefined && args.resultCode === 0) || (args.result !== undefined && args.result === 0)) { 228 if (args.want && args.want.parameters) { 229 selectResult.data = args.want.parameters.select_item_list; 230 } 231 } else if ((args.resultCode !== undefined && args.resultCode === -1) || (args.result !== undefined && args.result === 1)) { 232 selectResult.data = []; 233 } else { 234 selectResult.error = getErr(ErrCode.RESULT_ERROR); 235 } 236 237 console.log('[picker] Select selectResult: ' + JSON.stringify(selectResult)); 238 return selectResult; 239} 240 241async function documentPickerSelect(...args) { 242 let checkArgsResult = checkArguments(args); 243 if (checkArgsResult !== undefined) { 244 console.log('[picker] Select Invalid argument'); 245 throw checkArgsResult; 246 } 247 248 let context = undefined; 249 let config = undefined; 250 let result = undefined; 251 252 try { 253 context = getContext(this); 254 } catch (getContextError) { 255 console.log('[picker] getContext error: ' + getContextError); 256 return undefined; 257 } 258 try { 259 config = parseDocumentPickerSelectOption(args, ACTION.SELECT_ACTION_MODAL); 260 result = await context.requestDialogService(config); 261 } catch (paramError) { 262 console.log('[picker] Select paramError: ' + JSON.stringify(paramError)); 263 try { 264 config = parseDocumentPickerSelectOption(args, ACTION.SELECT_ACTION); 265 result = await context.startAbilityForResult(config, {windowMode: 0}); 266 } catch (error) { 267 console.log('[picker] Select error: ' + error); 268 return undefined; 269 } 270 } 271 console.log('[picker] Select result: ' + JSON.stringify(result)); 272 try { 273 const selectResult = getDocumentPickerSelectResult(result); 274 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] === 'function') { 275 return args[ARGS_ONE](selectResult.error, selectResult.data); 276 } else if (args.length === ARGS_ONE && typeof args[ARGS_ZERO] === 'function') { 277 return args[ARGS_ZERO](selectResult.error, selectResult.data); 278 } 279 return new Promise((resolve, reject) => { 280 if (selectResult.data !== undefined) { 281 resolve(selectResult.data); 282 } else { 283 reject(selectResult.error); 284 } 285 }) 286 } catch (resultError) { 287 console.log('[picker] Result error: ' + resultError); 288 } 289 return undefined; 290} 291 292function parseDocumentPickerSaveOption(args, action) { 293 let config = { 294 action: action, 295 parameters: { 296 startMode: 'save', 297 } 298 }; 299 300 if (args.length > ARGS_ZERO && typeof args[ARGS_ZERO] === 'object') { 301 let option = args[ARGS_ZERO]; 302 if ((option.newFileNames !== undefined) && option.newFileNames.length > 0) { 303 config.parameters.key_pick_file_name = option.newFileNames; 304 config.parameters.saveFile = option.newFileNames[0]; 305 } 306 307 if (option.defaultFilePathUri !== undefined) { 308 config.parameters.key_pick_dir_path = option.defaultFilePathUri; 309 } 310 if ((option.fileSuffixChoices !== undefined) && option.fileSuffixChoices.length > 0) { 311 config.parameters.key_file_suffix_choices = option.fileSuffixChoices; 312 } 313 } 314 315 console.log('[picker] Save config: ' + JSON.stringify(config)); 316 return config; 317} 318 319function getDocumentPickerSaveResult(args) { 320 let saveResult = { 321 error: undefined, 322 data: undefined 323 }; 324 325 // 0:success 326 // -1:Non modal cancel 327 // 1:Modal cancel 328 // ResultCode is a non modal return code. 329 // Result is the return code of the modality. 330 if ((args.resultCode !== undefined && args.resultCode === 0) || (args.result !== undefined && args.result === 0)) { 331 if (args.want && args.want.parameters) { 332 saveResult.data = args.want.parameters.pick_path_return; 333 } 334 } else if ((args.resultCode !== undefined && args.resultCode === -1) || (args.result !== undefined && args.result === 1)) { 335 saveResult.data = []; 336 } else { 337 saveResult.error = getErr(ErrCode.RESULT_ERROR); 338 } 339 340 console.log('[picker] Save saveResult: ' + JSON.stringify(saveResult)); 341 return saveResult; 342} 343 344async function documentPickerSave(...args) { 345 let checkArgsResult = checkArguments(args); 346 if (checkArgsResult !== undefined) { 347 console.log('[picker] Invalid argument'); 348 throw checkArgsResult; 349 } 350 351 let context = undefined; 352 let config = undefined; 353 let result = undefined; 354 355 try { 356 context = getContext(this); 357 } catch (getContextError) { 358 console.log('[picker] getContext error: ' + getContextError); 359 return undefined; 360 } 361 try { 362 config = parseDocumentPickerSaveOption(args, ACTION.SAVE_ACTION_MODAL); 363 result = await context.requestDialogService(config); 364 } catch (paramError) { 365 console.log('[picker] paramError: ' + JSON.stringify(paramError)); 366 try { 367 config = parseDocumentPickerSaveOption(args, ACTION.SAVE_ACTION); 368 result = await context.startAbilityForResult(config, {windowMode: 0}); 369 } catch (error) { 370 console.log('[picker] error: ' + error); 371 return undefined; 372 } 373 } 374 console.log('[picker] Save result: ' + JSON.stringify(result)); 375 try { 376 const saveResult = getDocumentPickerSaveResult(result); 377 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] === 'function') { 378 return args[ARGS_ONE](saveResult.error, saveResult.data); 379 } else if (args.length === ARGS_ONE && typeof args[ARGS_ZERO] === 'function') { 380 return args[ARGS_ZERO](saveResult.error, saveResult.data); 381 } 382 return new Promise((resolve, reject) => { 383 if (saveResult.data !== undefined) { 384 resolve(saveResult.data); 385 } else { 386 reject(saveResult.error); 387 } 388 }) 389 } catch (resultError) { 390 console.log('[picker] Result error: ' + resultError); 391 } 392 return undefined; 393} 394 395async function audioPickerSelect(...args) { 396 let checkArgsResult = checkArguments(args); 397 if (checkArgsResult !== undefined) { 398 console.log('[picker] Invalid argument'); 399 throw checkArgsResult; 400 } 401 402 const config = parseDocumentPickerSelectOption(args, ACTION.SELECT_ACTION); 403 console.log('[picker] config: ' + JSON.stringify(config)); 404 405 try { 406 let context = getContext(this); 407 let result = await context.startAbilityForResult(config, {windowMode: 0}); 408 console.log('[picker] result: ' + JSON.stringify(result)); 409 const selectResult = getDocumentPickerSelectResult(result); 410 console.log('[picker] selectResult: ' + JSON.stringify(selectResult)); 411 if (args.length === ARGS_TWO && typeof args[ARGS_ONE] === 'function') { 412 return args[ARGS_ONE](selectResult.error, selectResult.data); 413 } else if (args.length === ARGS_ONE && typeof args[ARGS_ZERO] === 'function') { 414 return args[ARGS_ZERO](selectResult.error, selectResult.data); 415 } 416 return new Promise((resolve, reject) => { 417 if (selectResult.data !== undefined) { 418 resolve(selectResult.data); 419 } else { 420 reject(selectResult.error); 421 } 422 }) 423 } catch (error) { 424 console.log('[picker] error: ' + error); 425 } 426 return undefined; 427} 428 429function PhotoSelectOptions() { 430 this.MIMEType = PhotoViewMIMETypes.INVALID_TYPE; 431 this.maxSelectNumber = -1; 432} 433 434function PhotoSelectResult(uris, isOriginalPhoto) { 435 this.photoUris = uris; 436 this.isOriginalPhoto = isOriginalPhoto; 437} 438 439function PhotoSaveOptions() { 440 this.newFileNames = undefined; 441} 442 443function DocumentSelectOptions() { 444 this.defaultFilePathUri = undefined; 445 this.fileSuffixFilters = undefined; 446 this.maxSelectNumber = undefined; 447} 448 449function DocumentSaveOptions() { 450 this.newFileNames = undefined; 451 this.defaultFilePathUri = undefined; 452 this.fileSuffixChoices = undefined; 453} 454 455function AudioSelectOptions() {} 456 457function AudioSaveOptions() { 458 this.newFileNames = undefined; 459} 460 461function PhotoViewPicker() { 462 this.select = photoPickerSelect; 463 this.save = documentPickerSave; 464} 465 466function DocumentViewPicker() { 467 this.select = documentPickerSelect; 468 this.save = documentPickerSave; 469} 470 471function AudioViewPicker() { 472 this.select = audioPickerSelect; 473 this.save = documentPickerSave; 474} 475 476export default { 477 PhotoViewMIMETypes : PhotoViewMIMETypes, 478 PhotoSelectOptions : PhotoSelectOptions, 479 PhotoSelectResult : PhotoSelectResult, 480 PhotoSaveOptions : PhotoSaveOptions, 481 DocumentSelectOptions : DocumentSelectOptions, 482 DocumentSaveOptions : DocumentSaveOptions, 483 AudioSelectOptions : AudioSelectOptions, 484 AudioSaveOptions : AudioSaveOptions, 485 PhotoViewPicker : PhotoViewPicker, 486 DocumentViewPicker: DocumentViewPicker, 487 AudioViewPicker : AudioViewPicker, 488}