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