1/*
2 * Copyright (c) 2022-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
16import { Log } from '../../../utils/Log';
17import { MediaDataSource } from './MediaDataSource';
18import type { LoadingListener } from '../LoadingListener';
19import { BroadCast } from '../../../utils/BroadCast';
20import { Constants as PhotoConstants } from './Constants';
21import { MediaItem } from './MediaItem';
22import { Constants } from '../../common/Constants';
23import { BrowserDataFactory } from '../../../interface/BrowserDataFactory';
24import { AlbumDefine } from '../AlbumDefine';
25import { BroadCastConstants } from '../../common/BroadCastConstants';
26import { BroadCastManager } from '../../common/BroadCastManager';
27import { ImageUtil } from '../../../utils/ImageUtil';
28import { ScreenManager } from '../../common/ScreenManager';
29
30import display from '@ohos.display';
31import { FileAsset, UserFileManagerAccess } from '../../../access/UserFileManagerAccess';
32
33const TAG: string = 'common_PhotoDataSource';
34
35// DataSource
36export class PhotoDataSource implements IDataSource, LoadingListener {
37  static readonly MEIDA_URL_PREFIX_STR = 'datashare:///media/';
38  static readonly ALBUM_URL_PREFIX_STR = 'datashare:///media/album/';
39  static readonly IMAGE_URL_PREFIX_STR = 'datashare:///media/image/';
40  static readonly VIDEO_URL_PREFIX_STR = 'datashare:///media/video/';
41  static readonly IMAGE_URL_PREFIX_STR_V10 = 'file://media/image/';
42  static readonly VIDEO_URL_PREFIX_STR_V10 = 'file://media/video/';
43  static readonly MEIDA_URL_PREFIX_STR_V10 = 'file://media/';
44  static readonly ALBUM_URL_PREFIX_STR_V10 = 'file://media/PhotoAlbum/';
45  static readonly IMAGE_VIDEO_URL_PREFIX_STR_V10 = 'file://media/Photo/';
46
47  static readonly IMAGE_TYPE_PREFIX_STR = 'image/';
48  static readonly VIDEO_TYPE_PREFIX_STR = 'video/';
49  // Data change listener
50  albumUri: string = undefined;
51  protected photoDataImpl;
52  private lastTotalCount = -1;
53  private albumDataSource: MediaDataSource;
54  private broadCast: BroadCast;
55  private currentIndex = 0;
56  private deviceId: string = ''
57  private enableGetDataFlag: boolean = true;
58
59  constructor(albumUri?: string) {
60    Log.debug(TAG, 'bind onMessage');
61    if (albumUri) {
62      this.albumUri = albumUri;
63    }
64
65    this.photoDataImpl = BrowserDataFactory.getFeature(BrowserDataFactory.TYPE_PHOTO);
66  }
67
68  totalCount(): number {
69    let newTotalCount = 0;
70    if (this.albumDataSource) {
71      newTotalCount = this.albumDataSource.mediaCount;
72      if (this.lastTotalCount != newTotalCount) {
73        Log.info(TAG, `totalCount: ${newTotalCount}`);
74        this.lastTotalCount = newTotalCount;
75      }
76      if (newTotalCount > 0 && !this.albumDataSource.getFirstRawData()) {
77        return 0;
78      }
79    }
80    return newTotalCount || 0;
81  }
82
83  realTotalCount(): number {
84    let totalCount = 0;
85    if (this.albumDataSource) {
86      totalCount = this.albumDataSource.realTotalCount();
87      Log.debug(TAG, `realTotalCount: ${totalCount}`);
88      return totalCount;
89    }
90    return 0;
91  }
92
93  // get DataIndex with item
94  getDataIndex(item: MediaItem): number {
95    return this.albumDataSource.getDataIndex(item);
96  }
97
98  resetAlbumUri(albumUri: string) {
99    this.albumUri = albumUri;
100    if (this.albumDataSource) {
101      this.albumDataSource.setAlbumUri(this.albumUri);
102      this.albumDataSource.resetActiveWindow();
103      this.albumDataSource.initData();
104      this.albumDataSource.forceUpdate();
105    }
106  }
107
108  setAlbumUri(albumUri: string) {
109    this.albumUri = albumUri;
110  }
111
112  getAlbumUri(): string {
113    return this.albumUri;
114  }
115
116  setDeviceId(deviceId: string) {
117    this.deviceId = deviceId;
118  }
119
120  releaseCache(key: string): void {
121  }
122
123  getItemIndexByUri(uri: string, indexNotifyCallback: Function): void {
124    Log.info(TAG, `getItemIndexByUri, ${uri}`);
125    this.albumDataSource.getItemIndexByUri(uri, indexNotifyCallback);
126  }
127
128  initData() {
129    let dataSource: MediaDataSource = new MediaDataSource(Constants.DEFAULT_SLIDING_WIN_SIZE);
130    dataSource.setAlbumUri(this.albumUri);
131    dataSource.initData();
132    this.setAlbumDataSource(dataSource);
133  }
134
135  enableGetData(flag: boolean): void {
136    this.enableGetDataFlag = flag;
137    if (this.albumDataSource) {
138      this.albumDataSource.enableGetData(flag);
139    }
140  }
141
142  // LazyForEach call
143  getData(index: number): any {
144    if (!this.enableGetDataFlag) {
145      return undefined;
146    }
147    Log.info(TAG, `getData index ${index}`);
148     if (this.albumDataSource) {
149       this.albumDataSource.updateSlidingWindow(index);
150       let mediaItem: MediaItem = this.albumDataSource.getRawData(index);
151       return this.packData(index, mediaItem);
152     }
153    return undefined;
154  }
155
156  packData(index: number, mediaItem: MediaItem) {
157    if (!mediaItem) {
158      Log.error(TAG, `Get item undefined, index: ${index}`);
159      return undefined;
160    }
161    if (mediaItem.height == 0 || mediaItem.width == 0) {
162      this.getDataByUri(mediaItem.uri).then((result) => {
163        mediaItem = new MediaItem(result);
164        if (mediaItem.height == 0 || mediaItem.width == 0) {
165          return;
166        }
167        let index = this.albumDataSource?.getIndexByMediaItem(mediaItem) ?? -1;
168        if (index != -1) {
169          this.albumDataSource?.onDataChanged(index);
170        }
171        this.onDataChanged(index);
172      })
173    }
174    let imgWidth = mediaItem.width;
175    let imgHeight = mediaItem.height;
176    let scale = this.convertDecodeSize(mediaItem.width, mediaItem.height);
177    Log.debug(TAG, `packData imgWidth: ${imgWidth} imgHeight: ${imgHeight} scale: ${scale}`);
178    if (scale != 0) {
179      const NEAR_PIX: number = 0.01;
180      mediaItem.imgWidth = Math.floor(mediaItem.width * scale);
181      mediaItem.imgHeight = Math.floor(mediaItem.height * scale);
182      imgWidth = Math.floor(imgWidth * scale + NEAR_PIX);
183      imgHeight = Math.floor(imgHeight * scale + NEAR_PIX);
184    }
185    Log.debug(TAG, `packData imgWidth: ${imgWidth} imgHeight: ${imgHeight}}`);
186
187    return {
188      data: mediaItem,
189      pos: index,
190      thumbnail: this.photoDataImpl.getThumbnail(mediaItem.uri, mediaItem.path, { height: imgHeight, width: imgWidth })
191    };
192  }
193
194  updatePixMapDataSource(index: number): void {
195    this.currentIndex = index;
196    // cache load.
197  }
198
199  getRawData(index: number): any {
200    if (this.albumDataSource) {
201      return {
202        data: this.albumDataSource.getRawData(index),
203        pos: index
204      };
205    }
206
207    Log.warn(TAG, `albumDataSource is undefined for index:${index}`);
208
209    return {
210      data: null,
211      pos: index
212    };
213  }
214
215  registerDataChangeListener(listener: DataChangeListener): void {
216    Log.info(TAG, 'registerDataChangeListener');
217    if (this.albumDataSource) {
218      this.albumDataSource.registerObserver();
219      if (this.albumDataSource.listeners.indexOf(listener) < 0) {
220        this.albumDataSource.listeners.push(listener);
221      }
222      Log.debug(TAG, `listener size: ${this.albumDataSource.listeners.length}`);
223    }
224
225  }
226
227  unregisterDataChangeListener(listener: DataChangeListener): void {
228    Log.info(TAG, 'unregisterDataChangeListener');
229    if (this.albumDataSource) {
230      const pos = this.albumDataSource.listeners.indexOf(listener);
231      if (pos >= 0) {
232        this.albumDataSource.listeners.splice(pos, 1);
233      }
234      Log.debug(TAG, `unregisterDataChangeListener listener size: ${this.albumDataSource.listeners.length}`);
235    }
236  }
237
238  setAlbumDataSource(albumDataSource: MediaDataSource): void {
239    Log.debug(TAG, 'setAlbumDataSource');
240    if (!albumDataSource) {
241      Log.error(TAG, 'dataSource undefined');
242    }
243    this.albumDataSource = albumDataSource;
244    this.albumDataSource.addLoadingListener(this);
245  }
246
247  getAlbumDataSource(): MediaDataSource | undefined {
248    if (this.albumDataSource) {
249      return this.albumDataSource;
250    }
251    return undefined;
252  }
253
254  setBroadCast(broadCastParam: BroadCast): void {
255    this.broadCast = broadCastParam;
256  }
257
258  setBroadCastToAlbum(broadCastParam: BroadCast) {
259    if (this.albumDataSource) {
260      this.albumDataSource.setPhotoBroadCast(broadCastParam);
261    }
262  }
263
264  onDataReloaded() {
265    Log.info(TAG, `onDataReloaded start`);
266    if (this.albumDataSource) {
267      this.albumDataSource.onDataReloaded();
268    }
269  }
270
271  onSizeLoadingFinished(size: number): void {
272    Log.info(TAG, `onSizeLoadingFinished, current size: ${size}`);
273    this.broadCast?.emit(PhotoConstants.DATA_SIZE_CHANGED, [size]);
274  }
275
276  onDataLoadingFinished(): void {
277    // after the mediaLib updates the data, it directly calls onDataReloaded.
278    // swiper updates only the five mounted items
279    Log.debug(TAG, `onDataLoadingFinished listeners size: ${this.albumDataSource.listeners.length}\
280            totalCount: ${this.totalCount()}`);
281    this.broadCast?.emit(PhotoConstants.DATA_CONTENT_CHANGED, []);
282  }
283
284  onDataChanged(dataIndex: number): void {
285    if (this.albumDataSource) {
286      this.albumDataSource.listeners.forEach(listener => {
287        listener.onDataChanged(dataIndex);
288      })
289    }
290  }
291
292  deleteData(index: number) {
293    if (this.albumDataSource) {
294      this.albumDataSource.listeners.forEach(listener => {
295        Log.debug(TAG, 'onDataDeleted');
296        listener.onDataDeleted(index);
297      })
298    }
299  }
300
301  async getDataByUri(uri: string): Promise<FileAsset> {
302    let tmp: FileAsset = await this.photoDataImpl.getDataByUri(uri) as FileAsset;
303    return tmp;
304  }
305
306  getItemByUri(uri: string): any {
307    if (this.albumDataSource) {
308      return this.albumDataSource.getItemByUri(uri);
309    }
310    return null;
311  }
312
313  getDataIndexByUri(uri: string): number {
314    if (this.albumDataSource) {
315      return this.albumDataSource.getDataIndexByUri(uri);
316    }
317    return Constants.NOT_FOUND;
318  }
319  // Search for the index of first valid item in MediaDataSource
320  getValidStartIndex(): number {
321    if (this.albumDataSource) {
322      return this.albumDataSource.getValidStartIndex();
323    }
324    return Constants.NOT_FOUND;
325  }
326  // Search for the index of last valid item in MediaDataSource
327  getValidEndIndex(): number {
328    if (this.albumDataSource) {
329      return this.albumDataSource.getValidEndIndex();
330    }
331    return Constants.NOT_FOUND;
332  }
333
334  public release(): void {
335    if (this.albumDataSource) {
336      this.albumDataSource.releasePhotoBroadCast();
337      this.albumDataSource.removeLoadingListener(this);
338      // cancel the mediaLibrary listening of albumDataSource when the large image is destroyed.
339      // cannot cancel it in unregisterDataChangeListener
340      this.albumDataSource && this.albumDataSource.unregisterObserver();
341    }
342
343    this.broadCast = null;
344  }
345
346  getPositionByIndex(index: number): number {
347    if (this.albumDataSource) {
348      return this.albumDataSource.getPositionByIndex(index);
349    }
350  }
351
352  onChange(changeType) {
353    if (this.albumDataSource) {
354      this.albumDataSource.onActive();
355      this.albumDataSource.onChange(changeType);
356    }
357  }
358
359  switchRefreshOn() {
360    if (this.albumDataSource) {
361      this.albumDataSource.switchRefreshOn();
362    }
363  };
364
365  private convertDecodeSize(imageWidth: number, imageHeight: number): number {
366    const HOLD_SCALE: number = 1.0;
367    if (imageWidth <= 0 || imageHeight <= 0) {
368      return HOLD_SCALE;
369    }
370    let displayClass: display.Display = display.getDefaultDisplaySync();
371    let screenWidth: number = displayClass.width;
372    let screenHeight: number = displayClass.height;
373    if (screenWidth <= 0 || screenHeight <= 0) {
374      return HOLD_SCALE;
375    }
376    let scale = HOLD_SCALE;
377    let desiredScale = screenHeight / screenWidth;
378    let sourceScale = imageHeight / imageWidth;
379
380    if (sourceScale > desiredScale) {
381      scale = screenHeight / imageHeight;
382    } else {
383      scale = screenWidth / imageWidth;
384    }
385    return scale < HOLD_SCALE ? scale : HOLD_SCALE;
386  }
387
388  private generateSampleSize(imageWidth: number, imageHeight: number, index: number): number {
389    let width = vp2px(ScreenManager.getInstance().getWinWidth());
390    let height = vp2px(ScreenManager.getInstance().getWinHeight());
391    width = width == 0 ? ScreenManager.DEFAULT_WIDTH : width;
392    height = height == 0 ? ScreenManager.DEFAULT_HEIGHT : height;
393    let maxNumOfPixels
394    if (this.currentIndex == index) {
395      maxNumOfPixels = 2 * width * height;
396    } else {
397      maxNumOfPixels = width * height;
398    }
399    let minSide = Math.min(width, height);
400    return ImageUtil.computeSampleSize(imageWidth, imageHeight, minSide, maxNumOfPixels);
401  }
402}