• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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