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 { MediaItem } from './MediaItem';
17import { ViewType } from './ViewType';
18import { AbsDataSource } from '../AbsDataSource';
19import { GetItemsCallback } from './GetItemsCallback';
20import { GetMediaCountCallback } from './GetMediaCountCallback';
21import { Log } from '../../../utils/Log';
22import { BroadCastConstants } from '../../common/BroadCastConstants';
23import { BroadCast } from '../../../utils/BroadCast';
24import { BroadCastManager } from '../../common/BroadCastManager';
25import { Constants } from '../../common/Constants';
26import { PendingTask } from './PendingTask';
27import type { PendingCondition } from './PendingCondition';
28import { TraceControllerUtils } from '../../../utils/TraceControllerUtils';
29import { AlbumDefine } from '../AlbumDefine';
30import { BrowserDataFactory } from '../../../interface/BrowserDataFactory';
31import type { BrowserDataInterface } from '../../../interface/BrowserDataInterface';
32import type { QueryParam } from '../BrowserDataImpl';
33import type { ViewData } from './ViewData';
34import { GetItemIndexCallback } from './GetItemIndexCallback';
35import { FileAsset } from '../../../access/UserFileManagerAccess';
36import type { PhotoDataImpl } from './PhotoDataImpl';
37import { GetItemCallback } from './GetItemCallback';
38
39const TAG: string = 'common_MediaDataSource';
40
41export class MediaDataSource extends AbsDataSource {
42  // Number of first pictures loaded during initialization
43  private static INIT_FIRST_PATCH_LOAD_COUNT = 50;
44  initDataTraceName: string = 'PhotoGridPageInitData';
45
46  // Album list, all photos, etc. may involve album aggregation display, so multiple albums are required
47  photoDataImpl: BrowserDataInterface;
48
49  // Number of elements in layout
50  size: number = 0;
51  realSize: number = 0;
52  getItemCountFinish: boolean = false;
53
54  // Number of media in the dataset
55  mediaCount: number = 0;
56
57  // Is the quantity changed
58  isCountChanged: boolean = false;
59
60  // Is the quantity changed
61  isCountReduced: boolean = false;
62
63  addedCount: number = Constants.NUMBER_0;
64
65  // Save the amount of data of a window size
66  items: MediaItem[] = [];
67
68  // window Size
69  windowSize: number = 0;
70
71  // start point
72  activeStart: number = 0;
73
74  // end point
75  activeEnd: number = 0;
76
77  // layoutIndex to dataIndex
78  dataIndexes: number[] = [];
79
80  // dataIndex to layoutIndex
81  layoutIndexes: number[] = [];
82  broadCast: BroadCast;
83  photosBroadCast: BroadCast;
84
85  // The BroadCast of the application process. Event registration and destruction should be paired
86  appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast();
87
88  // Whether to delay updating data
89  isPendingUpdateData: boolean = true;
90
91  // During initialization, the task to data updates before the delay when count returns
92  pendingUpdateData: PendingTask;
93
94  // Request time of the first batch of data
95  firstPatchDataRequestTime: number;
96
97  // 删除ID,通知统一使用uri,准确查询整机相册中的资源,图库再通过uri识别系统相册。
98  albumUri: string = undefined;
99  filterMediaType: string = undefined;
100  isRefresh: boolean = false;
101  isEditSaveReload: boolean = false;
102  validEndIndex: number;
103  private browserActiveCallback: Function = this.onPhotoBrowserActive.bind(this);
104  private enableGetDataFlag: boolean = true;
105
106  constructor(windowSize: number, photoDataImpl?: PhotoDataImpl) {
107    super();
108    this.photoDataImpl = photoDataImpl ?? BrowserDataFactory.getFeature(BrowserDataFactory.TYPE_PHOTO) as PhotoDataImpl;
109    this.windowSize = windowSize;
110    this.activeEnd = windowSize;
111    this.items = new Array(windowSize);
112    this.appBroadCast.on(BroadCastConstants.PHOTO_BROWSER_ACTIVE, this.browserActiveCallback);
113  }
114
115  releaseBroadCast(): void {
116    Log.debug(TAG, 'release all appBroadCast');
117    this.appBroadCast.off(BroadCastConstants.PHOTO_BROWSER_ACTIVE, this.browserActiveCallback);
118  }
119
120  totalCount(): number {
121    if (this.lastTotalCount != this.size) {
122      Log.info(TAG, `totalCount: ${this.size}`);
123      this.lastTotalCount = this.size;
124    }
125    return this.size;
126  }
127
128  isEmpty(): boolean {
129    return this.totalCount() === 0 && (this.items.length <= 0 || this.items[0] === undefined);
130  }
131
132  realTotalCount(): number {
133    Log.info(TAG, `realTotalCount: ${this.size}, ${this.getItemCountFinish}`);
134    if (this.getItemCountFinish == true) {
135      return this.size;
136    }
137    return -1;
138  }
139
140  getData(index: number): ViewData {
141    if (!this.enableGetDataFlag) {
142      return undefined;
143    }
144    this.updateSlidingWindow(this.dataIndexes[index]);
145    let result = this.getWrappedData(index);
146    if (result == null) {
147      return undefined;
148    }
149    return result;
150  }
151
152  // get raw MediaItem data
153  getRawData(dataIndex: number): MediaItem {
154    if (dataIndex < this.activeStart || dataIndex >= this.activeEnd) {
155      Log.warn(TAG, `dataIndex is invalid: ${dataIndex}, ${this.activeStart} ~ ${this.activeEnd}`);
156      return undefined;
157    }
158    return this.items[dataIndex - this.activeStart];
159  }
160
161  getFirstRawData(): MediaItem {
162    if (this.items.length <= 0) {
163      return undefined;
164    }
165    return this.items[0];
166  }
167
168  // get DataIndex with item
169  getDataIndex(item: MediaItem): number {
170    for (let i = 0; i < this.items.length; i++) {
171      if (this.items[i] != undefined && this.items[i].uri === item.uri) {
172        return i + this.activeStart;
173      }
174    }
175    return Constants.NOT_FOUND;
176  }
177
178  getItemByUri(uri: string): MediaItem {
179    for (let i = 0; i < this.items.length; i++) {
180      if (this.items[i] != undefined && this.items[i].uri == uri) {
181        return this.items[i];
182      }
183    }
184    return null;
185  }
186
187  getDataIndexByUri(uri: string): number {
188    for (let i = 0; i < this.items.length; i++) {
189      if (this.items[i] != undefined && this.items[i].uri === uri) {
190        return i + this.activeStart;
191      }
192    }
193    return Constants.NOT_FOUND;
194  }
195  // Search for the index of first valid item in 500 items
196  getValidStartIndex(): number {
197    for (let i = 0; i < this.items.length; i++) {
198      if (this.items[i]) {
199        return i + this.activeStart;
200      }
201    }
202    return Constants.NOT_FOUND;
203  }
204  // Search for the index of last valid item in 500 items
205  getValidEndIndex(): number {
206    return this.validEndIndex;
207  }
208
209  getMediaItemByUri(uri: string): MediaItem {
210    if (this.items.length <= 0) {
211      return undefined;
212    }
213    for (let item of this.items) {
214      if (item != undefined && item.uri === uri) {
215        return item;
216      }
217    }
218    return undefined;
219  }
220
221  getMediaItemByUriFromAll(uri: string, itemNotifyCallback: Function): MediaItem {
222    Log.info(TAG, `getMediaItemByUriFromAll uri ${uri}`);
223    if (this.items.length <= 0) {
224      return undefined;
225    }
226
227    for (let item of this.items) {
228      if (item?.uri === uri) {
229        return item;
230      }
231    }
232
233    // 若当前数据不存在于当前列表的处理
234    Log.info(TAG, `getMediaItemByUriFromAll, ${uri}`);
235    let itemIndexCallback: GetItemCallback = new GetItemCallback(this, itemNotifyCallback);
236    this.photoDataImpl.getMediaItemByUri(itemIndexCallback, uri);
237
238    return undefined;
239  }
240
241  getMediaCount(): number {
242    return this.mediaCount;
243  }
244
245  resetActiveWindow(): void {
246    this.activeStart = 0;
247    this.activeEnd = this.windowSize;
248    this.items = new Array<MediaItem>(this.windowSize);
249  }
250
251  // Initialize the first batch of data
252  initData(): void {
253    TraceControllerUtils.startTrace(this.initDataTraceName);
254    this.getItemCountFinish = false;
255    Log.info(TAG, `initData, ${this.getItemCountFinish}`);
256    this.pendingUpdateData = new PendingTask(<PendingCondition> {
257      shouldPending: () => {
258        return this.isPendingUpdateData;
259      }
260    });
261    let start = 0;
262    let limit = MediaDataSource.INIT_FIRST_PATCH_LOAD_COUNT;
263    this.firstPatchDataRequestTime = Date.now();
264    this.lastUpdateTime = this.firstPatchDataRequestTime;
265    let firstPatchDataCallback = {
266      callback: (assets: MediaItem[], dataAlbumUri?: string): void => {
267        Log.info(TAG, `took  ${(Date.now() - this.firstPatchDataRequestTime)}\
268          milliseconds to load first batch: ${(assets == null ? 0 : assets.length)}`);
269        if (this.isInvalidData(this.albumUri, dataAlbumUri)) {
270          Log.error(TAG, 'initData callback isInvalidData:this.albumUri:' + this.albumUri + ' dataAlbumUri:' + dataAlbumUri);
271          return;
272        }
273        if (assets?.length > 0) {
274          this.updateMediaData(this.firstPatchDataRequestTime, start, assets);
275        }
276        if (assets?.length < limit) {
277          return;
278        }
279        let secondPatchDataCallback: GetItemsCallback = new GetItemsCallback(this, limit);
280        let secondParam: QueryParam = {
281          albumUri: this.albumUri,
282          start: limit,
283          count: this.windowSize - limit,
284        };
285        if (this.filterMediaType != undefined) {
286          secondParam.filterMediaType = this.filterMediaType;
287        }
288        this.photoDataImpl.getData(secondPatchDataCallback, secondParam);
289      }
290    };
291    let firstParam: QueryParam = {
292      albumUri: this.albumUri,
293      start: start,
294      count: limit,
295    };
296    if (this.filterMediaType != undefined) {
297      firstParam.filterMediaType = this.filterMediaType;
298    }
299    Log.info(TAG, `queryparam ${JSON.stringify(firstParam)}`);
300    this.photoDataImpl.getData(firstPatchDataCallback, firstParam);
301    this.loadData();
302  }
303
304  // Query quantity
305  loadData(): void {
306    Log.info(TAG, `loadData, ${this.getItemCountFinish}`);
307    let initCountCallback: GetMediaCountCallback = new GetMediaCountCallback(this);
308    if (this.filterMediaType != undefined) {
309      this.photoDataImpl.getDataCount(initCountCallback, {
310        albumUri: this.albumUri,
311        filterMediaType: this.filterMediaType
312      });
313    } else {
314      this.photoDataImpl.getDataCount(initCountCallback, { albumUri: this.albumUri });
315    }
316  }
317
318  getItemIndexByUri(uri: string, indexNotifyCallback: Function): void {
319    Log.info(TAG, `getItemIndexByUri, ${uri}`);
320    let itemIndexCallback: GetItemIndexCallback = new GetItemIndexCallback(this, indexNotifyCallback);
321    if (this.filterMediaType) {
322      this.photoDataImpl.getDataIndexByUri(itemIndexCallback, {
323        albumUri: this.albumUri,
324        filterMediaType: this.filterMediaType
325      }, uri);
326    } else {
327      this.photoDataImpl.getDataIndexByUri(itemIndexCallback, { albumUri: this.albumUri }, uri);
328    }
329  }
330
331  // update media count
332  updateMediaCount(requestTime: number, count: number): void {
333    TraceControllerUtils.startTraceWithTaskId('updateMediaCount', requestTime);
334    Log.info(TAG, `updateMediaCount requestTime: ${requestTime}, count: ${count}, real size: ${this.realSize}`);
335    this.lastUpdateTime = requestTime;
336
337    this.getItemCountFinish = true;
338    this.addedCount = (this.realSize > Constants.NUMBER_0) ? (count - this.realSize) : Constants.NUMBER_0;
339    this.realSize = count;
340    this.updateSize(requestTime, count);
341
342    TraceControllerUtils.finishTraceWithTaskId('updateMediaCount', requestTime);
343    if (this.isPendingUpdateData) {
344      this.isPendingUpdateData = false;
345      this.pendingUpdateData.flush();
346    }
347  }
348
349  /**
350   * Update related variables of count
351   *
352   * @param requestTime
353   * @param count
354   */
355  updateSize(requestTime: number, count: number): void {
356    Log.info(TAG, `updateSize, old size: ${this.size}, new size: ${count}`)
357    this.isCountChanged = count != this.size;
358    this.isCountReduced = count < this.size;
359    this.mediaCount = count;
360    this.size = this.mediaCount;
361    this.dataIndexes = [];
362    this.layoutIndexes = [];
363    for (let i = 0; i < this.size; i++) {
364      this.dataIndexes.push(i);
365      this.layoutIndexes.push(i);
366    }
367
368    this.updateCountPostProcess();
369  }
370
371  /**
372   * run after update count,Adjust sliding windows and query items as needed
373   */
374  updateCountPostProcess(): void {
375    Log.info(TAG, 'updateCountPostProcess');
376    // Exclude initData
377    if (this.hasNewChange) {
378      // when the total count less to activeEnd, the window need to change
379      if (this.activeEnd > this.mediaCount) {
380        let newActiveStart = Math.max(0, this.activeStart - (this.activeEnd - this.mediaCount));
381        let newActiveEnd = newActiveStart + this.windowSize;
382        // data reuse
383        if (newActiveEnd > this.activeStart) {
384          this.dataReuse(newActiveStart, this.activeStart, newActiveEnd);
385        }
386        this.activeStart = newActiveStart;
387        this.activeEnd = newActiveEnd;
388        Log.info(TAG, `updateSlidingWindow, newActiveStart:
389                ${this.activeStart} , newActiveEnd:${this.activeEnd}`);
390      }
391
392      if (this.mediaCount == 0) {
393        this.hasNewChange = false;
394        this.onDataReloaded();
395      } else if (this.isEditSaveReload || this.isCountChanged || this.isRefresh) {
396        // dirty data, need to get data again
397        Log.info(TAG, 'dirty data, need to get data again');
398        let callback: GetItemsCallback = new GetItemsCallback(this, this.activeStart);
399        let param: QueryParam = {
400          albumUri: this.albumUri,
401          start: this.activeStart,
402          count: this.windowSize,
403        };
404        if (this.filterMediaType != undefined) {
405          param.filterMediaType = this.filterMediaType;
406        }
407        this.photoDataImpl.getData(callback, param);
408      } else {
409        this.hasNewChange = false;
410      }
411    }
412    this.emitCountUpdateCallbacks();
413  }
414
415  emitCountUpdateCallbacks(): void {
416    this.mCallbacks['updateCount'] && this.mCallbacks['updateCount'](this.mediaCount);
417    this.broadCast && this.broadCast.emit(Constants.ON_LOADING_FINISHED, [this.mediaCount]);
418    this.notifySizeLoadingFinished(this.mediaCount);
419  }
420
421  // Update data in data callback
422  updateMediaData(requestTime: number, start: number, mediaItems: MediaItem[]): void {
423    if (requestTime == this.firstPatchDataRequestTime && this.isPendingUpdateData && this.size == 0) {
424      Log.info(TAG, 'the callback of mediaItems is earlier than that of count.');
425      this.updateCountThroughMediaItems(requestTime, mediaItems);
426      this.mediaDataUpdateExecutor(requestTime, start, mediaItems);
427      TraceControllerUtils.finishTrace(this.initDataTraceName);
428    } else if (this.isPendingUpdateData) {
429      Log.info(TAG, 'isPendingUpdateData execute start');
430      this.pendingUpdateData.execute(() => {
431        this.mediaDataUpdateExecutor(requestTime, start, mediaItems);
432        TraceControllerUtils.finishTrace(this.initDataTraceName);
433      });
434    } else {
435      this.mediaDataUpdateExecutor(requestTime, start, mediaItems);
436      TraceControllerUtils.finishTrace(this.initDataTraceName);
437    }
438  }
439
440  /**
441   * Update count through items
442   *
443   * @param requestTime
444   * @param mediaItems mediaItems
445   */
446  updateCountThroughMediaItems(requestTime: number, mediaItems: MediaItem[]): void {
447    Log.info(TAG, 'updateCountThroughMediaItems');
448    this.updateSize(0, mediaItems == null ? 0 : mediaItems.length);
449  }
450
451  /**
452   * Actual execution function of items update
453   *
454   * @param requestTime
455   * @param start items
456   * @param mediaItems mediaItems
457   */
458  mediaDataUpdateExecutor(requestTime: number, start: number, mediaItems: MediaItem[]): void {
459    TraceControllerUtils.startTraceWithTaskId('updateMediaData', requestTime);
460    Log.info(TAG, `updateMediaData requestTime: ${requestTime}, start: ${start}, this.addedCount: ${this.addedCount}, this.isEditSaveReload: ${this.isEditSaveReload}`);
461    if (this.lastUpdateTime < this.lastChangeTime && this.isActive) {
462      Log.info(TAG, 'request data expired, request again!');
463      this.loadData();
464    } else {
465      this.hasNewChange = false;
466    }
467
468    if (mediaItems == undefined || mediaItems.length == 0) {
469      Log.error(TAG, 'results are empty!');
470      return;
471    }
472
473    if (start >= this.activeEnd || start + mediaItems.length <= this.activeStart) {
474      Log.info(TAG, 'results are not in active window');
475      return;
476    }
477
478    Log.info(TAG, `updateMediaData mediaItems.length: ${mediaItems.length}`);
479    let fromIndex = Math.max(start, this.activeStart);
480    let endIndex = Math.min(this.activeEnd, start + mediaItems.length);
481    this.validEndIndex = endIndex - 1;
482    Log.info(TAG, `updateMediaData listeners size ${this.listeners.length}`)
483
484    for (let i = fromIndex; i < endIndex; i++) {
485      this.items[i - this.activeStart] = mediaItems[i - start];
486      Log.debug(TAG, `updateMediaData ${this.layoutIndexes[i]}, ${mediaItems[i - start].uri}, ${mediaItems[i - start].getTitle()}`);
487    }
488
489    if (this.isCountChanged || this.isRefresh) {
490      if (this.photosBroadCast && (this.isCountReduced || this.isRefresh || (this.addedCount > Constants.NUMBER_0)) && !this.isEditSaveReload) {
491        this.photosBroadCast.emit(BroadCastConstants.ON_DATA_RELOADED, [this.addedCount]);
492        this.addedCount = Constants.NUMBER_0;
493      } else if (this.broadCast) {
494        this.broadCast.emit(BroadCastConstants.ON_DATA_RELOADED, []);
495      } else {
496        this.onDataReloaded();
497      }
498
499      this.isCountChanged = false;
500      this.isCountReduced = false;
501      this.isRefresh = false;
502    } else {
503      for (let i = fromIndex; i < endIndex; i++) {
504        this.onDataChanged(this.layoutIndexes[i]);
505      }
506    }
507
508    if (this.isEditSaveReload) {
509      if (this.addedCount >= 0) {
510        this.photosBroadCast && this.photosBroadCast.emit(BroadCastConstants.ON_DATA_RELOADED_WITH_EDIT, []);
511        this.addedCount = Constants.NUMBER_0;
512      }
513    } else {
514      this.notifyDataLoadingFinished();
515    }
516    TraceControllerUtils.finishTraceWithTaskId('updateMediaData', requestTime);
517  }
518
519  enableGetData(flag: boolean): void {
520    this.enableGetDataFlag = flag;
521  }
522
523  // Update sliding window
524  public updateSlidingWindow(dataIndex: number): void {
525    if (dataIndex == Constants.INVALID || dataIndex == undefined) {
526      return;
527    }
528    let windowCenter = Math.round((this.activeStart + this.activeEnd) / 2);
529    if (Math.abs(dataIndex - windowCenter) < Constants.STEP) {
530      return;
531    }
532    if (dataIndex < windowCenter && this.activeStart == 0) {
533      return;
534    }
535    if (dataIndex > windowCenter && this.activeEnd >= this.mediaCount) {
536      return;
537    }
538    let newActiveStart = this.getWindowActiveStart(dataIndex, windowCenter);
539    let newActiveEnd = newActiveStart + this.windowSize;
540    let requestStart = newActiveStart;
541    let requestCount = this.windowSize;
542    Log.info(TAG, `dataIndex: ${dataIndex}, windowCenter: ${windowCenter}, newActiveStart=${newActiveStart}`
543    + `, newActiveEnd=${newActiveEnd}, requestStart=${requestStart}, requestCount=${requestCount}`);
544
545    if (newActiveEnd < this.activeStart || newActiveStart > this.activeEnd) {
546      let limit = MediaDataSource.INIT_FIRST_PATCH_LOAD_COUNT;
547      let start = Math.max(0, (newActiveStart + newActiveEnd) / 2 - limit / 2);
548      this.firstPatchDataRequestTime = Date.now();
549      this.lastUpdateTime = this.firstPatchDataRequestTime;
550      let firstPatchDataCallback = {
551        callback: (assets: MediaItem[], dataAlbumUri?: string): void => {
552          Log.info(TAG, `took  ${(Date.now() - this.firstPatchDataRequestTime)}\
553                     milliseconds to load first batch: ${(assets == null ? 0 : assets.length)}`);
554          if (this.isInvalidData(this.albumUri, dataAlbumUri)) {
555            Log.error(TAG, 'updateSlidingWindow callback isInvalidData:this.albumUri:' + this.getAlbumUri() + ' dataAlbumUri:' + dataAlbumUri);
556            return;
557          }
558          if (assets?.length > 0) {
559            this.updateMediaData(this.firstPatchDataRequestTime, start, assets);
560          }
561          if (assets?.length < limit) {
562            return;
563          }
564          let secondPatchDataCallback: GetItemsCallback = new GetItemsCallback(this, newActiveStart);
565          let secondParam: QueryParam = {
566            albumUri: this.albumUri,
567            start: newActiveStart,
568            count: this.windowSize
569          };
570          if (this.filterMediaType != undefined) {
571            secondParam.filterMediaType = this.filterMediaType;
572          }
573          this.photoDataImpl.getData(secondPatchDataCallback, secondParam);
574        }
575      };
576      let firstParam: QueryParam = {
577        albumUri: this.albumUri,
578        start: start,
579        count: limit
580      };
581      if (this.filterMediaType != undefined) {
582        firstParam.filterMediaType = this.filterMediaType;
583      }
584      this.photoDataImpl.getData(firstPatchDataCallback, firstParam);
585    }
586
587    if (newActiveEnd < this.activeEnd && newActiveEnd > this.activeStart) {
588      requestCount = this.activeStart - newActiveStart;
589      this.dataReuse(newActiveStart, this.activeStart, newActiveEnd);
590    }
591    if (newActiveStart > this.activeStart && newActiveStart < this.activeEnd) {
592      requestStart = this.activeEnd;
593      requestCount = newActiveEnd - this.activeEnd;
594      this.dataReuse(newActiveStart, newActiveStart, this.activeEnd);
595    }
596    if (newActiveStart > this.activeEnd || newActiveEnd < this.activeStart) {
597      this.items = new Array(this.windowSize);
598    }
599    Log.info(TAG, `activeStart=${this.activeStart}, activeEnd=${this.activeEnd}, newActiveStart=${newActiveStart}`
600    + `, newActiveEnd=${newActiveEnd}, requestStart=${requestStart}, requestCount=${requestCount}`);
601    this.activeStart = newActiveStart;
602    this.activeEnd = newActiveEnd;
603
604    let callback: GetItemsCallback = new GetItemsCallback(this, requestStart);
605    let param: QueryParam = {
606      albumUri: this.albumUri,
607      start: requestStart,
608      count: requestCount
609    };
610    if (this.filterMediaType != undefined) {
611      param.filterMediaType = this.filterMediaType;
612    }
613    this.photoDataImpl.getData(callback, param);
614  }
615
616  getMediaItemSafely(index: number): MediaItem {
617    let mediaItem: MediaItem = this.items[index];
618    if (mediaItem == null) {
619      Log.error(TAG, `invalid data, index:${index}, active Start:${this.activeStart}, End:${this.activeEnd}`);
620      mediaItem = new MediaItem(null);
621    }
622    return mediaItem;
623  }
624
625  // Forced refresh interface
626  forceUpdate() {
627    this.onDataReloaded();
628  }
629
630  // Packaging data for the view layer
631  getWrappedData(index: number): ViewData {
632    let mediaItemIndex: number = this.dataIndexes[index] - this.activeStart;
633    if (mediaItemIndex < 0 || mediaItemIndex >= this.items.length) {
634      return undefined;
635    }
636    let result = {
637      viewType: ViewType.ITEM,
638      mediaItem: this.getMediaItemSafely(mediaItemIndex),
639      viewIndex: index
640    };
641    return result;
642  }
643
644  setAlbumUri(uri: string): void {
645    Log.info(TAG, `setAlbumUri: ${uri}`)
646    this.albumUri = uri;
647  }
648
649  getAlbumUri(): string {
650    return this.albumUri;
651  }
652
653  setFilterMediaType(filterMediaType: string): void {
654    Log.info(TAG, `set filterMediaType: ${filterMediaType}`)
655    this.filterMediaType = filterMediaType;
656  }
657
658  setBroadCast(broadCastParam: BroadCast): void {
659    this.broadCast = broadCastParam;
660  }
661
662  setPhotoBroadCast(broadCastParam: BroadCast): void {
663    this.photosBroadCast = broadCastParam;
664  }
665
666  releasePhotoBroadCast(): void {
667    this.photosBroadCast = null;
668  }
669
670  onPhotoBrowserActive(isActive: boolean, transition: string): void {
671    if (transition == Constants.PHOTO_TRANSITION_ALBUM || transition == Constants.PHOTO_TRANSITION_CAMERA) {
672      if (isActive) {
673        Log.info(TAG, 'onPhotoBrowserActive')
674        this.onActive();
675      } else {
676        this.onInActive();
677      }
678    } else if (transition == Constants.PHOTO_TRANSITION_EDIT) {
679      if (isActive) {
680        this.isEditSaveReload = true;
681        this.onActive();
682      } else {
683        this.isEditSaveReload = false;
684      }
685    }
686  }
687
688  getIndexByMediaItem(item: MediaItem): number {
689    for (let index = 0; index < this.items.length; index++) {
690      if (item.uri == this.items[index].uri) {
691        this.items[index] = item
692        return index;
693      }
694    }
695    return -1;
696  }
697
698  // 父类方法需要子类覆写
699  getPositionByIndex(index: number): number {
700    return index;
701  }
702
703  onChange(mediaType: string): void {
704    if (mediaType === 'image' || mediaType === 'video' || mediaType === 'album') {
705      Log.debug(TAG, `local onChange ${mediaType}`);
706      super.onChange(mediaType);
707    }
708  }
709
710  switchRefreshOn(): void {
711    this.isRefresh = true
712  }
713
714  getGroupCountBeforeItem(item: MediaItem): number {
715    return 0;
716  }
717
718  private getWindowActiveStart(dataIndex: number, windowCenter: number): number {
719    let isForward = (dataIndex > windowCenter);
720    let halfWinSize = Math.round(this.windowSize / 2);
721    // The end of the window does not exceed the total amount of data when the window moves forward.
722    if (isForward) {
723      let forwardStep = dataIndex - windowCenter;
724      forwardStep = forwardStep % Constants.STEP == 0
725        ? forwardStep
726        : Math.ceil(forwardStep / Constants.STEP) * Constants.STEP;
727      let forwardStepSize = Math.min(forwardStep, Math.abs(this.mediaCount - this.activeEnd));
728      Log.info(TAG, `forwardStep=${forwardStep}, stepSize=${forwardStepSize}, activeStart=${this.activeStart}`);
729      return (this.activeStart + forwardStepSize);
730    } else {
731      let backwardStep = windowCenter - dataIndex;
732      backwardStep = backwardStep % Constants.STEP == 0
733        ? backwardStep
734        : Math.ceil(backwardStep / Constants.STEP) * Constants.STEP;
735      Log.info(TAG, `backward step ${backwardStep}, activeStart=${this.activeStart}`);
736      return Math.max(0, this.activeStart - backwardStep);
737    }
738  }
739
740  /**
741   * data Reuse
742   *
743   * @param newActiveStart
744   * @param startIndex
745   * @param endIndex
746   */
747  private dataReuse(newActiveStart: number, startIndex: number, endIndex: number): void {
748    let newData: MediaItem[] = new Array(this.windowSize);
749    for (let i = startIndex; i < endIndex; i++) {
750      newData[i - newActiveStart] = this.items[i - this.activeStart];
751    }
752    this.items = newData;
753  }
754
755  isInvalidData(requestUri: string, dataUri: string): boolean {
756    if (dataUri === '' || dataUri === undefined || dataUri === null) {
757      return false;
758    }
759    return !(requestUri === dataUri);
760  }
761}