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