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}