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