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