• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 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
16import photoAccessHelper from '@ohos.file.photoAccessHelper';
17import { fileIo as fs, fileUri } from '@kit.CoreFileKit';
18import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit';
19import { bundleManager } from '@kit.AbilityKit';
20import { LengthMetrics, LengthUnit } from '@kit.ArkUI';
21
22const PHOTO_VIEW_MIME_TYPE_MAP = new Map([
23    ['*/*', 'FILTER_MEDIA_TYPE_ALL'],
24    ['image/*', 'FILTER_MEDIA_TYPE_IMAGE'],
25    ['video/*', 'FILTER_MEDIA_TYPE_VIDEO'],
26    ['image/movingPhoto', 'FILTER_MEDIA_TYPE_IMAGE_MOVING_PHOTO']
27])
28
29interface MimeTypeFilter {
30  mimeTypeArray: string[],
31  filterType: number
32}
33
34@Component
35export struct PhotoPickerComponent {
36  pickerOptions?: PickerOptions | undefined;
37  onSelect?: (uri: string) => void;
38  onDeselect?: (uri: string) => void;
39  onItemClicked?: (itemInfo: ItemInfo, clickType: ClickType) => boolean;
40  onEnterPhotoBrowser?: (photoBrowserInfo: PhotoBrowserInfo) => boolean;
41  onExitPhotoBrowser?: (photoBrowserInfo: PhotoBrowserInfo) => boolean;
42  onPickerControllerReady?: () => void;
43  onPhotoBrowserChanged?: (browserItemInfo: BaseItemInfo) => boolean;
44  onSelectedItemsDeleted?: ItemsDeletedCallback;
45  onExceedMaxSelected?: ExceedMaxSelectedCallback;
46  onCurrentAlbumDeleted?: CurrentAlbumDeletedCallback;
47  onVideoPlayStateChanged?: VideoPlayStateChangedCallback;
48  @ObjectLink @Watch('onChanged') pickerController: PickerController;
49  private proxy: UIExtensionProxy | undefined;
50  @State revokeIndex = 0;
51
52  private onChanged(): void {
53    if (!this.proxy) {
54      return;
55    }
56    let data = this.pickerController?.data;
57    if (data?.has('SET_SELECTED_URIS')) {
58      this.proxy.send({ 'selectUris': data?.get('SET_SELECTED_URIS') as Array<string> });
59      console.info('PhotoPickerComponent onChanged: SET_SELECTED_URIS');
60    } else if (data?.has('SET_ALBUM_URI')) {
61      this.proxy.send({ 'albumUri': data?.get('SET_ALBUM_URI') as string });
62      console.info('PhotoPickerComponent onChanged: SET_ALBUM_URI');
63    } else if (data?.has('SET_MAX_SELECT_COUNT')) {
64      this.onSetMaxSelectCount(data);
65    } else if (data?.has('SET_PHOTO_BROWSER_ITEM')) {
66      this.onSetPhotoBrowserItem(data);
67    } else {
68      this.otherOnChange(data);
69    }
70  }
71
72  private otherOnChange(data?: Map<string, Object>) {
73    if (data?.has('EXIT_PHOTO_BROWSER')) {
74      this.handleExitPhotoBrowser();
75    } else if (data?.has('SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY')) {
76      this.onSetPhotoBrowserUIElementVisibility(data);
77    } else if (data?.has('CREATE_URI')) {
78      this.onCreateUri(data);
79      console.info('PhotoPickerComponent onChanged: CREATE_URI');
80    } else if (data?.has('REPLACE_URI')) {
81      this.onReplaceUri(data);
82      console.info('PhotoPickerComponent onChanged: REPLACE_URI');
83    } else if (data?.has('SAVE_TRUSTED_PHOTO_ASSETS')) {
84      this.onSaveTrustedPhotoAssets(data);
85      console.info('PhotoPickerComponent onChanged: SAVE_REPLACE_PHOTO_ASSETS');
86    } else {
87      console.info('PhotoPickerComponent onChanged: other case');
88    }
89  }
90
91  private onSetMaxSelectCount(data?: Map<string, Object>): void {
92    let maxSelected: MaxSelected = data?.get('SET_MAX_SELECT_COUNT') as MaxSelected;
93    let map: Map<MaxCountType, number> | undefined = maxSelected?.data;
94    this.proxy.send({
95      'totalCount': map?.get(MaxCountType.TOTAL_MAX_COUNT),
96      'photoCount': map?.get(MaxCountType.PHOTO_MAX_COUNT),
97      'videoCount': map?.get(MaxCountType.VIDEO_MAX_COUNT)
98    });
99    console.info('PhotoPickerComponent onChanged: SET_MAX_SELECT_COUNT');
100  }
101
102  private onSetPhotoBrowserItem(data?: Map<string, Object>): void {
103    let photoBrowserRangeInfo: PhotoBrowserRangeInfo = data?.get('SET_PHOTO_BROWSER_ITEM') as PhotoBrowserRangeInfo;
104    this.proxy?.send({
105      'itemUri': photoBrowserRangeInfo?.uri,
106      'photoBrowserRange': photoBrowserRangeInfo?.photoBrowserRange
107    });
108    console.info('PhotoPickerComponent onChanged: SET_PHOTO_BROWSER_ITEM');
109  }
110
111  private handleExitPhotoBrowser(): void {
112    this.proxy.send({ 'exitPhotoBrowser': true });
113    console.info('PhotoPickerComponent onChanged: EXIT_PHOTO_BROWSER');
114  }
115
116  private onSetPhotoBrowserUIElementVisibility(data?: Map<string, Object>): void {
117    let photoBrowserUIElementVisibility: PhotoBrowserUIElementVisibility =
118      data?.get('SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY') as PhotoBrowserUIElementVisibility;
119    this.proxy?.send({
120      'elements': photoBrowserUIElementVisibility?.elements,
121      'isVisible': photoBrowserUIElementVisibility?.isVisible
122    });
123    console.info('PhotoPickerComponent onChanged: SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY');
124  }
125
126  private onCreateUri(data?: Map<string, Object>): void {
127    let array = data?.get('CREATE_URI') as Array<Object>;
128    this.proxy?.send({
129      selectedMediaUri: array[0],
130      createUri: array[1],
131      date: array[2]
132    });
133    console.info('PhotoPickerComponent onChanged CREATE_URI');
134  }
135
136  private onReplaceUri(data?: Map<string, Object>): void {
137    let array = data?.get('REPLACE_URI') as Array<Object>;
138    this.proxy?.send({
139      oriUri: array[0],
140      replaceUri: array[1],
141      date: array[2]
142    });
143    console.info('PhotoPickerComponent onChanged REPLACE_URI');
144  }
145
146  private onSaveTrustedPhotoAssets(data?: Map<string, Object>): void {
147    let array: Array<object> = data?.get('SAVE_TRUSTED_PHOTO_ASSETS') as Array<object>;
148    this.proxy?.send({
149      replaceUris: array[0],
150      config: array[1],
151      saveMode: array[2],
152      appName: array[3],
153      date: array[4]
154    });
155    console.info('PhotoPickerComponent onChanged SAVE_REPLACE_PHOTO_ASSETS');
156  }
157
158  build() {
159    Row() {
160      Column() {
161        SecurityUIExtensionComponent({
162          parameters: {
163            errorRevokeIndex: this.revokeIndex,
164            "ability.want.params.uiExtensionTargetType": "photoPicker",
165            uri: "multipleselect",
166            targetPage: "photoPage",
167            filterMediaType: this.convertMIMETypeToFilterType(this.pickerOptions?.MIMEType),
168            mimeTypeFilter: this.parseMimeTypeFilter(this.pickerOptions?.mimeTypeFilter),
169            fileSizeFilter: this.pickerOptions?.fileSizeFilter,
170            videoDurationFilter: this.pickerOptions?.videoDurationFilter,
171            photoViewMimeTypeFileSizeFilters: this.pickerOptions?.photoViewMimeTypeFileSizeFilters,
172            maxSelectNumber: this.pickerOptions?.maxSelectNumber as number,
173            isPhotoTakingSupported: this.pickerOptions?.isPhotoTakingSupported as boolean,
174            isEditSupported: false,
175            recommendationOptions: this.pickerOptions?.recommendationOptions as photoAccessHelper.RecommendationOptions,
176            preselectedUri: this.pickerOptions?.preselectedUris as Array<string>,
177            isFromPickerView: true,
178            isNeedActionBar: false,
179            isNeedSelectBar: false,
180            isSearchSupported: this.pickerOptions?.isSearchSupported as boolean,
181            checkBoxColor: this.pickerOptions?.checkBoxColor as string,
182            backgroundColor: this.pickerOptions?.backgroundColor as string,
183            checkboxTextColor: this.pickerOptions?.checkboxTextColor as string,
184            photoBrowserBackgroundColorMode: this.pickerOptions?.photoBrowserBackgroundColorMode as PickerColorMode,
185            isRepeatSelectSupported: this.pickerOptions?.isRepeatSelectSupported as boolean,
186            maxSelectedReminderMode: this.pickerOptions?.maxSelectedReminderMode as ReminderMode,
187            orientation: this.pickerOptions?.orientation as PickerOrientation,
188            selectMode: this.pickerOptions?.selectMode as SelectMode,
189            maxPhotoSelectNumber: this.pickerOptions?.maxPhotoSelectNumber as number,
190            maxVideoSelectNumber: this.pickerOptions?.maxVideoSelectNumber as number,
191            isOnItemClickedSet: this.onItemClicked ? true : false,
192            isPreviewForSingleSelectionSupported: this.pickerOptions?.isPreviewForSingleSelectionSupported as boolean,
193            singleSelectionMode: this.pickerOptions?.singleSelectionMode as number,
194            isSlidingSelectionSupported: this.pickerOptions?.isSlidingSelectionSupported as boolean,
195            photoBrowserCheckboxPosition: this.pickerOptions?.photoBrowserCheckboxPosition as [number, number],
196            gridMargin: this.pickerOptions?.gridMargin as Margin,
197            photoBrowserMargin: this.pickerOptions?.photoBrowserMargin as Margin,
198            gridStartOffset: this.pickerOptions?.gridStartOffset as number,
199            gridEndOffset: this.pickerOptions?.gridEndOffset as number,
200            singleLineConfig: this.getSingleLineConfig(this.pickerOptions?.singleLineConfig as SingleLineConfig),
201            uiComponentColorMode: this.pickerOptions?.uiComponentColorMode as PickerColorMode,
202            combinedMediaTypeFilter: this.pickerOptions?.combinedMediaTypeFilter as Array<string>,
203          }
204        })
205          .height('100%')
206          .width('100%')
207          .onRemoteReady((proxy) => {
208            this.proxy = proxy;
209            console.info('PhotoPickerComponent onRemoteReady');
210          })
211          .onReceive((data) => {
212            let wantParam: Record<string, Object> = data as Record<string, Object>;
213            this.handleOnReceive(wantParam);
214          })
215          .onError((error) => {
216            console.info('PhotoPickerComponent onError: ' + JSON.stringify(error));
217            console.info('PhotoPickerComponent revokeIndex: ' + this.revokeIndex);
218            if (error.code === 100014 && this.revokeIndex < 3) {
219              this.revokeIndex++;
220            }
221          });
222      }
223      .width('100%')
224    }
225    .height('100%')
226  }
227
228  private handleOnReceive(wantParam: Record<string, Object>): void {
229    let dataType = wantParam['dataType'] as string;
230    console.info('PhotoPickerComponent onReceive: dataType = ' + dataType);
231    if (dataType === 'selectOrDeselect') {
232      this.handleSelectOrDeselect(wantParam);
233    } else if (dataType === 'itemClick') {
234      this.handleItemClick(wantParam);
235    } else if (dataType === 'onPhotoBrowserStateChanged') {
236      this.handleEnterOrExitPhotoBrowser(wantParam);
237    } else if (dataType === 'remoteReady') {
238      if (this.onPickerControllerReady) {
239        this.onPickerControllerReady();
240        console.info('PhotoPickerComponent onReceive: onPickerControllerReady');
241      }
242    } else if (dataType === 'onPhotoBrowserChanged') {
243      this.handlePhotoBrowserChange(wantParam);
244    } else if (dataType === 'onVideoPlayStateChanged') {
245      this.handleVideoPlayStateChange(wantParam)
246    } else if (dataType === 'replaceCallback') {
247      this.handleReplaceCallback(wantParam);
248    } else if (dataType === 'createCallback') {
249      this.handleCreateCallback(wantParam);
250    } else if (dataType === 'saveCallback') {
251      this.handleSaveCallback(wantParam);
252    } else if (dataType === 'onBackground') {
253      console.info('PhotoPickerComponent onReceive: onBackground');
254      this.revokeIndex = 0;
255    } else {
256      this.handleOtherOnReceive(wantParam);
257      console.info('PhotoPickerComponent onReceive: other case');
258    }
259    console.info('PhotoPickerComponent onReceive' + this.pickerController.encrypt(JSON.stringify(wantParam)));
260  }
261
262  private handleOtherOnReceive(wantParam: Record<string, Object>): void {
263    let dataType = wantParam.dataType as string;
264    if (dataType === 'exceedMaxSelected') {
265      if (this.onExceedMaxSelected) {
266        this.onExceedMaxSelected(wantParam.maxCountType as MaxCountType);
267      }
268    } else if (dataType === 'selectedItemsDeleted') {
269      if (this.onSelectedItemsDeleted) {
270        this.onSelectedItemsDeleted(wantParam.selectedItemInfos as Array<BaseItemInfo>);
271      }
272    } else if (dataType === 'currentAlbumDeleted') {
273      if (this.onCurrentAlbumDeleted) {
274        this.onCurrentAlbumDeleted();
275      }
276    } else {
277      console.info('PhotoPickerComponent onReceive: other case');
278    }
279  }
280
281  private handleSelectOrDeselect(wantParam: Record<string, Object>): void {
282    let isSelect: boolean = wantParam['isSelect'] as boolean;
283    if (isSelect) {
284      if (this.onSelect) {
285        this.onSelect(wantParam['select-item-list'] as string);
286        console.info('PhotoPickerComponent onReceive: onSelect');
287      }
288    } else {
289      if (this.onDeselect) {
290        this.onDeselect(wantParam['select-item-list'] as string);
291        console.info('PhotoPickerComponent onReceive: onDeselect');
292      }
293    }
294  }
295
296  private handleItemClick(wantParam: Record<string, Object>): void {
297    if (this.onItemClicked) {
298      let clickType: ClickType = ClickType.SELECTED;
299      let type = wantParam['clickType'] as string;
300      if (type === 'select') {
301        clickType = ClickType.SELECTED;
302      } else if (type === 'deselect') {
303        clickType = ClickType.DESELECTED;
304      } else {
305        console.info('PhotoPickerComponent onReceive: other clickType');
306      }
307      let itemInfo: ItemInfo = new ItemInfo();
308      let itemType: string = wantParam['itemType'] as string;
309      if (itemType === 'thumbnail') {
310        itemInfo.itemType = ItemType.THUMBNAIL;
311      } else if (itemType === 'camera') {
312        itemInfo.itemType = ItemType.CAMERA;
313      } else {
314        console.info('PhotoPickerComponent onReceive: other itemType');
315      }
316      itemInfo.uri = wantParam['uri'] as string;
317      itemInfo.mimeType = wantParam['mimeType'] as string;
318      itemInfo.width = wantParam['width'] as number;
319      itemInfo.height = wantParam['height'] as number;
320      itemInfo.size = wantParam['size'] as number;
321      itemInfo.duration = wantParam['duration'] as number;
322      let result: boolean = this.onItemClicked(itemInfo, clickType);
323      console.info('PhotoPickerComponent onReceive: onItemClicked = ' + clickType);
324      if (this.proxy) {
325        if (itemType === 'thumbnail' && clickType === ClickType.SELECTED) {
326          this.proxy.send({ 'clickConfirm': itemInfo.uri, 'isConfirm': result });
327          console.info('PhotoPickerComponent onReceive: click confirm: uri = ' +
328            this.pickerController.encrypt(itemInfo.uri) + 'isConfirm = ' + result);
329        }
330        if (itemType === 'camera') {
331          this.proxy.send({ 'enterCamera': result });
332          console.info('PhotoPickerComponent onReceive: enter camera ' + result);
333        }
334      }
335    }
336  }
337
338  private handleEnterOrExitPhotoBrowser(wantParam: Record<string, Object>): void {
339    let isEnter: boolean = wantParam['isEnter'] as boolean;
340    let photoBrowserInfo: PhotoBrowserInfo = new PhotoBrowserInfo();
341    photoBrowserInfo.animatorParams = new AnimatorParams();
342    photoBrowserInfo.animatorParams.duration = wantParam['duration'] as number;
343    photoBrowserInfo.animatorParams.curve = wantParam['curve'] as Curve | ICurve | string;
344    if (isEnter) {
345      if (this.onEnterPhotoBrowser) {
346        this.onEnterPhotoBrowser(photoBrowserInfo);
347      }
348    } else {
349      if (this.onExitPhotoBrowser) {
350        this.onExitPhotoBrowser(photoBrowserInfo);
351      }
352    }
353    console.info('PhotoPickerComponent onReceive: onPhotoBrowserStateChanged = ' + isEnter);
354  }
355
356  private handlePhotoBrowserChange(wantParam: Record<string, Object>): void {
357    let browserItemInfo: BaseItemInfo = new BaseItemInfo();
358    browserItemInfo.uri = wantParam['uri'] as string;
359    if (this.onPhotoBrowserChanged) {
360      this.onPhotoBrowserChanged(browserItemInfo);
361    }
362    console.info('PhotoPickerComponent onReceive: onPhotoBrowserChanged = ' +
363      this.pickerController.encrypt(browserItemInfo.uri));
364  }
365
366  private handleVideoPlayStateChange(wantParam: Record<string, Object>): void {
367    if (this.onVideoPlayStateChanged) {
368      this.onVideoPlayStateChanged(wantParam.state as VideoPlayerState)
369    }
370    console.info('PhotoPickerComponent onReceive: onVideoPlayStateChanged = ' + JSON.stringify(wantParam));
371  }
372
373  private handleCreateCallback(wantParam: Record<string, Object>): void {
374    this.pickerController.actionCreateCallback(wantParam['grantUri'] as string, wantParam['date'] as number,
375      wantParam['code'] as number, wantParam['message'] as string);
376    console.info('PhotoPickerComponent onReceive: handleCreateCallback');
377  }
378
379  private handleReplaceCallback(wantParam: Record<string, Object>): void {
380    this.pickerController.actionReplaceCallback(wantParam['date'] as number,
381      { 'name': '', 'code': wantParam['code'] as number, 'message': wantParam['message'] as string });
382    console.info('PhotoPickerComponent onReceive: handleReplaceCallback');
383  }
384
385  private handleSaveCallback(wantParam: Record<string, Object>): void {
386    this.pickerController.actionSaveCallback(wantParam['date'] as number,
387      { 'name': '', 'code': wantParam['code'] as number, 'message': wantParam['error'] as string },
388      wantParam['data'] as Array<string>);
389    console.info('PhotoPickerComponent onReceive: handleSaveCallback');
390  }
391
392  parseMimeTypeFilter(filter?: photoAccessHelper.MimeTypeFilter): object | undefined {
393    if (!filter) {
394      return undefined;
395    }
396    let MimeTypeFilterObj: photoAccessHelper.MimeTypeFilter = {
397      mimeTypeArray: [],
398    };
399    if (filter.mimeTypeArray) {
400      for (let mimeType of filter.mimeTypeArray) {
401        if (PHOTO_VIEW_MIME_TYPE_MAP.has(mimeType)) {
402          o.mimeTypeArray.push(PHOTO_VIEW_MIME_TYPE_MAP.get(mimeType));
403        } else {
404          o.mimeTypeArray.push(mimeType);
405        }
406      }
407    }
408    return MimeTypeFilterObj;
409  }
410
411  private convertMIMETypeToFilterType(mimeType: photoAccessHelper.PhotoViewMIMETypes): string {
412    let filterType: string;
413    if (PHOTO_VIEW_MIME_TYPE_MAP.has(filterType)) {
414      filterType = PHOTO_VIEW_MIME_TYPE_MAP.get(mimeType);
415    if (PHOTO_VIEW_MIME_TYPE_MAP.has(filterType)) {
416      filterType = PHOTO_VIEW_MIME_TYPE_MAP.get(mimeType);
417    } else {
418      filterType = PHOTO_VIEW_MIME_TYPE_MAP.get('*/*);
419      filterType = PHOTO_VIEW_MIME_TYPE_MAP.get('*/*);
420    }
421    console.info('PhotoPickerComponent convertMIMETypeToFilterType: ' + JSON.stringify(filterType));
422    return filterType;
423  }
424
425  private getSingleLineConfig(singleLineConfig: SingleLineConfig): SingleLineConfig | undefined {
426    if (singleLineConfig === null || singleLineConfig === undefined) {
427      return undefined;
428    }
429    singleLineConfig.itemDisplayRatio = (singleLineConfig.itemDisplayRatio === null ||
430      singleLineConfig.itemDisplayRatio === undefined) ? ItemDisplayRatio.SQUARE_RATIO :
431      singleLineConfig.itemDisplayRatio;
432    singleLineConfig.itemBorderRadius = this.getSingleLineConfigItemBorderRadius(singleLineConfig.itemBorderRadius);
433    singleLineConfig.itemGap = this.getLength(singleLineConfig.itemGap);
434    return singleLineConfig
435  }
436
437  private getSingleLineConfigItemBorderRadius(itemBorderRadius?: Length | BorderRadiuses |
438    LocalizedBorderRadiuses): Length | BorderRadiuses | LocalizedBorderRadiuses {
439    if (itemBorderRadius === undefined || itemBorderRadius === null) {
440      return 0;
441    }
442    if (typeof itemBorderRadius === 'number' || typeof itemBorderRadius === 'string') {
443      return itemBorderRadius;
444    }
445    if (this.hasOwnProp(itemBorderRadius, ['topStart', 'topEnd', 'bottomStart', 'bottomEnd'])) {
446      const localizedBorderRadiuses: LocalizedBorderRadiuses = {
447        topStart: LengthMetrics.vp(0),
448        topEnd: LengthMetrics.vp(0),
449        bottomStart: LengthMetrics.vp(0),
450        bottomEnd: LengthMetrics.vp(0),
451      };
452      const itemBorderRadiusValue = itemBorderRadius as LocalizedBorderRadiuses;
453      localizedBorderRadiuses.topStart = itemBorderRadiusValue.topStart ? itemBorderRadiusValue.topStart :
454        LengthMetrics.vp(0);
455      localizedBorderRadiuses.topEnd = itemBorderRadiusValue.topEnd ? itemBorderRadiusValue.topEnd :
456        LengthMetrics.vp(0);
457      localizedBorderRadiuses.bottomStart = itemBorderRadiusValue.bottomStart ? itemBorderRadiusValue.bottomStart :
458        LengthMetrics.vp(0);
459      localizedBorderRadiuses.bottomEnd = itemBorderRadiusValue.bottomEnd ? itemBorderRadiusValue.bottomEnd :
460        LengthMetrics.vp(0);
461      return localizedBorderRadiuses;
462    }
463    if (this.hasOwnProp(itemBorderRadius, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'])) {
464      const borderRadiuses: BorderRadiuses = {
465        topLeft: 0,
466        topRight: 0,
467        bottomLeft: 0,
468        bottomRight: 0
469      };
470      const borderRadiusesValue = itemBorderRadius as BorderRadiuses;
471      borderRadiuses.topLeft = this.getLength(borderRadiusesValue.topLeft);
472      borderRadiuses.topRight = this.getLength(borderRadiusesValue.topRight);
473      borderRadiuses.bottomLeft = this.getLength(borderRadiusesValue.bottomLeft);
474      borderRadiuses.bottomRight = this.getLength(borderRadiusesValue.bottomRight);
475      return borderRadiuses;
476    }
477    const borderRadiusesValue = itemBorderRadius as Resource;
478    const resource = LengthMetrics.resource(borderRadiusesValue);
479    if (LengthUnitUtils.getInstance().isValid(resource)) {
480      return LengthUnitUtils.getInstance().stringify(resource);
481    }
482    return 0;
483  }
484
485  getLength(prop?: Length): Length {
486    if (prop === undefined || prop === null) {
487      return 0;
488    }
489    if (typeof prop === 'number' || typeof prop === 'string') {
490      return prop;
491    }
492    const resource = LengthMetrics.resource(prop);
493    if (LengthUnitUtils.getInstance().isValid(resource)) {
494      return LengthUnitUtils.getInstance().stringify(resource);
495    }
496    return 0;
497  }
498
499  private hasOwnProp(obj: Object, props: string[]): boolean {
500    for (const key of Object.keys(obj)) {
501      if (props.includes(key)) {
502        return true;
503      }
504    }
505    return false;
506  }
507}
508
509class LengthUnitUtils {
510  private static instance: LengthUnitUtils;
511
512  private constructor() {
513  }
514
515  public static getInstance(): LengthUnitUtils {
516    if (!LengthUnitUtils.instance) {
517      LengthUnitUtils.instance = new LengthUnitUtils();
518    }
519    return LengthUnitUtils.instance;
520  }
521
522  public stringify(metrics: LengthMetrics): string {
523    if (null === metrics || undefined === metrics || typeof metrics !== 'object' || null === metrics.unit ||
524      undefined === metrics.unit || null === metrics.value || undefined === metrics.value) {
525      return '0vp';
526    }
527    switch (metrics.unit) {
528      case LengthUnit.PX:
529        return `${metrics.value}px`;
530      case LengthUnit.VP:
531        return `${metrics.value}vp`;
532      case LengthUnit.FP:
533        return `${metrics.value}fp`;
534      case LengthUnit.PERCENT:
535        return `${metrics.value}%`;
536      case LengthUnit.LPX:
537        return `${metrics.value}lpx`;
538      default:
539        return '0vp';
540    }
541  }
542
543  public isValid(metrics: LengthMetrics): boolean {
544    if (null === metrics || undefined === metrics || typeof metrics !== 'object' ||
545      null === metrics.value || undefined === metrics.value) {
546      return false;
547    }
548    return metrics.value > 0;
549  }
550}
551
552export type ItemsDeletedCallback = (baseItemInfos: Array<BaseItemInfo>) => void;
553
554export type ExceedMaxSelectedCallback = (exceedMaxCountType: MaxCountType) => void;
555
556export type CurrentAlbumDeletedCallback = () => void;
557
558export type VideoPlayStateChanged = (state: VideoPlayerState) => {} void;
559
560@Observed
561export class PickerController {
562  data?: Map<string, Object>;
563  replaceCallbackMap: Map<number, Object> = new Map<number, Object>();
564  saveCallbackMap: Map<number, Object> = new Map<number, Object>();
565  createCallbackMap: Map<number, Object> = new Map<number, Object>();
566
567  setData(type: DataType, data: Object) {
568    if (data === undefined) {
569      return;
570    }
571    if (type === DataType.SET_SELECTED_URIS) {
572      if (data instanceof Array) {
573        let uriLists: Array<string> = data as Array<string>;
574        if (uriLists) {
575          this.data = new Map([['SET_SELECTED_URIS', [...uriLists]]]);
576          console.info('PhotoPickerComponent SET_SELECTED_URIS' + this.encrypt(JSON.stringify(uriLists)));
577        }
578      }
579    } else if (type === DataType.SET_ALBUM_URI) {
580      let albumUri: string = data as string;
581      if (albumUri !== undefined) {
582        this.data = new Map([['SET_ALBUM_URI', albumUri]]);
583        console.info('PhotoPickerComponent SET_ALBUM_URI' + this.encrypt(JSON.stringify(albumUri)));
584      }
585    } else {
586      console.info('PhotoPickerComponent setData: other case');
587    }
588  }
589
590  setMaxSelected(maxSelected: MaxSelected) {
591    if (maxSelected) {
592      this.data = new Map([['SET_MAX_SELECT_COUNT', maxSelected]]);
593      console.info('PhotoPickerComponent SET_MAX_SELECT_COUNT' + JSON.stringify(maxSelected));
594    }
595  }
596
597  setPhotoBrowserItem(uri: string, photoBrowserRange?: PhotoBrowserRange) {
598    let photoBrowserRangeInfo: PhotoBrowserRangeInfo = new PhotoBrowserRangeInfo();
599    photoBrowserRangeInfo.uri = uri;
600    let newPhotoBrowserRange = photoBrowserRange ? photoBrowserRange : PhotoBrowserRange.ALL;
601    photoBrowserRangeInfo.photoBrowserRange = newPhotoBrowserRange;
602    this.data = new Map([['SET_PHOTO_BROWSER_ITEM', photoBrowserRangeInfo]]);
603    console.info('PhotoPickerComponent SET_PHOTO_BROWSER_ITEM' + this.encrypt(JSON.stringify(photoBrowserRangeInfo)));
604  }
605
606  exitPhotoBrowser() {
607    this.data = new Map([['EXIT_PHOTO_BROWSER', true]]);
608    console.info('PhotoPickerComponent EXIT_PHOTO_BROWSER');
609  }
610
611  setPhotoBrowserUIElementVisibility(elements: Array<PhotoBrowserUIElement>, isVisible?: boolean) {
612    let photoBrowserUIElementVisibility: PhotoBrowserUIElementVisibility = new PhotoBrowserUIElementVisibility();
613    photoBrowserUIElementVisibility.elements = elements;
614    photoBrowserUIElementVisibility.isVisible = isVisible;
615    this.data = new Map([['SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY', photoBrowserUIElementVisibility]]);
616    console.info('PhotoPickerComponent SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY ' +
617    JSON.stringify(photoBrowserUIElementVisibility));
618  }
619
620  private async getAppName(): Promise<string> {
621    let flags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY | // for appName
622    bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE | // for appName
623    bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO | // for appId
624    bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION; // for appInfo
625    let bundleInfo = bundleManager.getBundleInfoForSelfSync(flags);
626    let labelId = bundleInfo.appInfo.labelId;
627    let appName = '';
628    let moduleName = '';
629    for (let hapInfo of bundleInfo.hapModulesInfo) {
630      if (labelId === hapInfo.labelId) {
631        moduleName = hapInfo.name;
632      }
633    }
634    appName = await getContext(this).createModuleContext(moduleName).resourceManager.getStringValue(labelId);
635    return appName;
636  }
637
638  replacePhotoPickerPreview(selectedMediaUri: string, replaceUri: string, callback: AsyncCallback<void>) {
639    try {
640      let fd = fs.openSync(replaceUri).fd;
641      fs.close(fd);
642    } catch (err) {
643      callback({'code': 13900002, 'message': 'No such file', name: ''});
644      return;
645    }
646    let date = Math.random();
647    this.data = new Map([['CREATE_URI', [selectedMediaUri, replaceUri, date]]]);
648    this.createCallbackMap.set(date, (grantUri: string, code: number, message: string) => {
649      if (code !== 0) {
650        callback({ 'name': '', 'code': code, 'message': message });
651        return;
652      }
653      let createFd = 0;
654      let replaceFd = 0;
655      try {
656        createFd = fs.openSync(grantUri, fs.OpenMode.READ_WRITE).fd;
657        replaceFd = fs.openSync(replaceUri, fs.OpenMode.READ_ONLY).fd;
658        fs.copyFileSync(replaceFd, createFd);
659        this.data = new Map([['REPLACE_URI', [selectedMediaUri, grantUri, date]]]);
660        this.replaceCallbackMap.set(date, callback);
661      } catch (err) {
662        callback({ 'code': 14000011, 'message': 'System inner fail', name: '' });
663      } finally {
664        fs.close(createFd);
665        fs.close(replaceFd);
666      }
667    })
668  }
669
670  saveTrustedPhotoAssets(selectedMediaUris: Array<string>, callback: AsyncCallback<Array<string>>,
671    config?: Array<photoAccessHelper.PhotoCreationConfig>, saveMode?: SaveMode) {
672    if (!selectedMediaUris || selectedMediaUris.length === 0) {
673      callback({'code': 14000002, 'message': 'Invalid URI', name: ''}, []);
674      return;
675    }
676    this.getAppName().then((appName: string)=>{
677      let date = Math.random();
678      this.data = new Map([['SAVE_TRUSTED_PHOTO_ASSETS', [selectedMediaUris, config, saveMode, appName, date]]]);
679      this.saveCallbackMap.set(date, callback);
680    })
681    console.info('PhotoPickerComponent SAVE_TRUSTED_PHOTO_ASSETS ');
682  }
683
684  actionCreateCallback(grantUri: string, date: number, code: number, message: string) {
685    if (this.createCallbackMap.has(date)) {
686      let callback = this.createCallbackMap.get(date) as Function;
687      if (callback) {
688        callback(grantUri, code, message);
689        this.createCallbackMap.delete(date);
690      }
691    }
692  }
693
694  actionReplaceCallback(date: number, err: BusinessError) {
695    if (this.replaceCallbackMap.has(date)) {
696      let callback = this.replaceCallbackMap.get(date) as Function;
697      if (callback) {
698        callback(err);
699        this.replaceCallbackMap.delete(date);
700      }
701    }
702  }
703
704  actionSaveCallback(date: number, err: BusinessError, data: Array<string>) {
705    if (this.saveCallbackMap.has(date)) {
706      let callback = this.saveCallbackMap.get(date) as Function;
707      if (callback) {
708        callback(err, data);
709        this.saveCallbackMap.delete(date);
710      }
711    }
712  }
713
714  encrypt(data) {
715    if (!data || data?.indexOf('file:///data/storage/') !== -1) {
716      return '';
717    }
718    return data.replace(/(\/\w+)\./g, '/******.');
719  }
720}
721
722export class PickerOptions extends photoAccessHelper.BaseSelectOptions {
723  checkBoxColor?: string;
724  backgroundColor?: string;
725  isRepeatSelectSupported?: boolean;
726  checkboxTextColor?: string;
727  photoBrowserBackgroundColorMode?: PickerColorMode;
728  maxSelectedReminderMode?: ReminderMode;
729  orientation?: PickerOrientation;
730  selectMode?: SelectMode;
731  maxPhotoSelectNumber?: number;
732  maxVideoSelectNumber?: number;
733  isSlidingSelectionSupported?: boolean;
734  photoBrowserCheckboxPosition?: [number, number];
735  gridMargin?: Margin;
736  photoBrowserMargin?: Margin;
737  singleLineConfig?: SingleLineConfig;
738  uiComponentColorMode?: PickerColorMode;
739  combinedMediaTypeFilter?: Array<string>;
740}
741
742export class BaseItemInfo {
743  uri?: string;
744  mimeType?: string;
745  width?: number;
746  height?: number;
747  size?: number;
748  duration?: number;
749}
750
751export class ItemInfo extends BaseItemInfo {
752  itemType?: ItemType;
753}
754
755export class PhotoBrowserInfo {
756  animatorParams?: AnimatorParams;
757}
758
759export class AnimatorParams {
760  duration?: number;
761  curve?: Curve | ICurve | string;
762}
763
764export class MaxSelected {
765  data?: Map<MaxCountType, number>;
766}
767
768export class SingleLineConfig {
769  itemDisplayRatio?: ItemDisplayRatio;
770  itemBorderRadius?: Length | BorderRadiuses | LocalizedBorderRadiuses;
771  itemGap?: Length;
772
773  constructor() {
774    this.itemDisplayRatio = ItemDisplayRatio.SQUARE_RATIO;
775    this.itemBorderRadius = 0;
776    this.itemGap = 0;
777  }
778}
779
780class PhotoBrowserRangeInfo {
781  uri?: string;
782  photoBrowserRange?: PhotoBrowserRange;
783}
784
785class PhotoBrowserUIElementVisibility {
786  elements?: Array<PhotoBrowserUIElement>;
787  isVisible?: boolean;
788}
789
790export enum DataType {
791  SET_SELECTED_URIS = 1,
792  SET_ALBUM_URI = 2
793}
794
795export enum ItemType {
796  THUMBNAIL = 0,
797  CAMERA = 1
798}
799
800export enum ClickType {
801  SELECTED = 0,
802  DESELECTED = 1
803}
804
805export enum PickerOrientation {
806  VERTICAL = 0,
807  HORIZONTAL = 1
808}
809
810export enum SelectMode {
811  SINGLE_SELECT = 0,
812  MULTI_SELECT = 1
813}
814
815export enum PickerColorMode {
816  AUTO = 0,
817  LIGHT = 1,
818  DARK = 2
819}
820
821export enum ReminderMode {
822  NONE = 0,
823  TOAST = 1,
824  MASK = 2
825}
826
827export enum MaxCountType {
828  TOTAL_MAX_COUNT = 0,
829  PHOTO_MAX_COUNT = 1,
830  VIDEO_MAX_COUNT = 2
831}
832
833export enum PhotoBrowserRange {
834  ALL = 0,
835  SELECTED_ONLY = 1
836}
837
838export enum PhotoBrowserUIElement {
839  CHECKBOX = 0,
840  BACK_BUTTON = 1
841}
842
843export enum VideoPlayerState {
844  PLAYING = 0,
845  PAUSED = 1,
846  STOPPED = 3,
847  SEEK_START = 4,
848  SEEK_FINISH = 5
849}
850
851export enum SaveMode {
852  SAVE_AS = 0,
853  OVERWRITE = 1
854}
855
856export enum ItemDisplayRatio {
857  SQUARE_RATIO = 0,
858  ORIGINAL_SIZE_RATIO = 1
859}