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 if (this.albumDataSource) { 149 this.albumDataSource.updateSlidingWindow(index); 150 let mediaItem: MediaItem = this.albumDataSource.getRawData(index); 151 return this.packData(index, mediaItem); 152 } 153 return undefined; 154 } 155 156 packData(index: number, mediaItem: MediaItem) { 157 if (!mediaItem) { 158 Log.error(TAG, `Get item undefined, index: ${index}`); 159 return undefined; 160 } 161 if (mediaItem.height == 0 || mediaItem.width == 0) { 162 this.getDataByUri(mediaItem.uri).then((result) => { 163 mediaItem = new MediaItem(result); 164 if (mediaItem.height == 0 || mediaItem.width == 0) { 165 return; 166 } 167 let index = this.albumDataSource?.getIndexByMediaItem(mediaItem) ?? -1; 168 if (index != -1) { 169 this.albumDataSource?.onDataChanged(index); 170 } 171 this.onDataChanged(index); 172 }) 173 } 174 let imgWidth = mediaItem.width; 175 let imgHeight = mediaItem.height; 176 let scale = this.convertDecodeSize(mediaItem.width, mediaItem.height); 177 Log.debug(TAG, `packData imgWidth: ${imgWidth} imgHeight: ${imgHeight} scale: ${scale}`); 178 if (scale != 0) { 179 const NEAR_PIX: number = 0.01; 180 mediaItem.imgWidth = Math.floor(mediaItem.width * scale); 181 mediaItem.imgHeight = Math.floor(mediaItem.height * scale); 182 imgWidth = Math.floor(imgWidth * scale + NEAR_PIX); 183 imgHeight = Math.floor(imgHeight * scale + NEAR_PIX); 184 } 185 Log.debug(TAG, `packData imgWidth: ${imgWidth} imgHeight: ${imgHeight}}`); 186 187 return { 188 data: mediaItem, 189 pos: index, 190 thumbnail: this.photoDataImpl.getThumbnail(mediaItem.uri, mediaItem.path, { height: imgHeight, width: imgWidth }) 191 }; 192 } 193 194 updatePixMapDataSource(index: number): void { 195 this.currentIndex = index; 196 // cache load. 197 } 198 199 getRawData(index: number): any { 200 if (this.albumDataSource) { 201 return { 202 data: this.albumDataSource.getRawData(index), 203 pos: index 204 }; 205 } 206 207 Log.warn(TAG, `albumDataSource is undefined for index:${index}`); 208 209 return { 210 data: null, 211 pos: index 212 }; 213 } 214 215 registerDataChangeListener(listener: DataChangeListener): void { 216 Log.info(TAG, 'registerDataChangeListener'); 217 if (this.albumDataSource) { 218 this.albumDataSource.registerObserver(); 219 if (this.albumDataSource.listeners.indexOf(listener) < 0) { 220 this.albumDataSource.listeners.push(listener); 221 } 222 Log.debug(TAG, `listener size: ${this.albumDataSource.listeners.length}`); 223 } 224 225 } 226 227 unregisterDataChangeListener(listener: DataChangeListener): void { 228 Log.info(TAG, 'unregisterDataChangeListener'); 229 if (this.albumDataSource) { 230 const pos = this.albumDataSource.listeners.indexOf(listener); 231 if (pos >= 0) { 232 this.albumDataSource.listeners.splice(pos, 1); 233 } 234 Log.debug(TAG, `unregisterDataChangeListener listener size: ${this.albumDataSource.listeners.length}`); 235 } 236 } 237 238 setAlbumDataSource(albumDataSource: MediaDataSource): void { 239 Log.debug(TAG, 'setAlbumDataSource'); 240 if (!albumDataSource) { 241 Log.error(TAG, 'dataSource undefined'); 242 } 243 this.albumDataSource = albumDataSource; 244 this.albumDataSource.addLoadingListener(this); 245 } 246 247 getAlbumDataSource(): MediaDataSource | undefined { 248 if (this.albumDataSource) { 249 return this.albumDataSource; 250 } 251 return undefined; 252 } 253 254 setBroadCast(broadCastParam: BroadCast): void { 255 this.broadCast = broadCastParam; 256 } 257 258 setBroadCastToAlbum(broadCastParam: BroadCast) { 259 if (this.albumDataSource) { 260 this.albumDataSource.setPhotoBroadCast(broadCastParam); 261 } 262 } 263 264 onDataReloaded() { 265 Log.info(TAG, `onDataReloaded start`); 266 if (this.albumDataSource) { 267 this.albumDataSource.onDataReloaded(); 268 } 269 } 270 271 onSizeLoadingFinished(size: number): void { 272 Log.info(TAG, `onSizeLoadingFinished, current size: ${size}`); 273 this.broadCast?.emit(PhotoConstants.DATA_SIZE_CHANGED, [size]); 274 } 275 276 onDataLoadingFinished(): void { 277 // after the mediaLib updates the data, it directly calls onDataReloaded. 278 // swiper updates only the five mounted items 279 Log.debug(TAG, `onDataLoadingFinished listeners size: ${this.albumDataSource.listeners.length}\ 280 totalCount: ${this.totalCount()}`); 281 this.broadCast?.emit(PhotoConstants.DATA_CONTENT_CHANGED, []); 282 } 283 284 onDataChanged(dataIndex: number): void { 285 if (this.albumDataSource) { 286 this.albumDataSource.listeners.forEach(listener => { 287 listener.onDataChanged(dataIndex); 288 }) 289 } 290 } 291 292 deleteData(index: number) { 293 if (this.albumDataSource) { 294 this.albumDataSource.listeners.forEach(listener => { 295 Log.debug(TAG, 'onDataDeleted'); 296 listener.onDataDeleted(index); 297 }) 298 } 299 } 300 301 async getDataByUri(uri: string): Promise<FileAsset> { 302 let tmp: FileAsset = await this.photoDataImpl.getDataByUri(uri) as FileAsset; 303 return tmp; 304 } 305 306 getItemByUri(uri: string): any { 307 if (this.albumDataSource) { 308 return this.albumDataSource.getItemByUri(uri); 309 } 310 return null; 311 } 312 313 getDataIndexByUri(uri: string): number { 314 if (this.albumDataSource) { 315 return this.albumDataSource.getDataIndexByUri(uri); 316 } 317 return Constants.NOT_FOUND; 318 } 319 // Search for the index of first valid item in MediaDataSource 320 getValidStartIndex(): number { 321 if (this.albumDataSource) { 322 return this.albumDataSource.getValidStartIndex(); 323 } 324 return Constants.NOT_FOUND; 325 } 326 // Search for the index of last valid item in MediaDataSource 327 getValidEndIndex(): number { 328 if (this.albumDataSource) { 329 return this.albumDataSource.getValidEndIndex(); 330 } 331 return Constants.NOT_FOUND; 332 } 333 334 public release(): void { 335 if (this.albumDataSource) { 336 this.albumDataSource.releasePhotoBroadCast(); 337 this.albumDataSource.removeLoadingListener(this); 338 // cancel the mediaLibrary listening of albumDataSource when the large image is destroyed. 339 // cannot cancel it in unregisterDataChangeListener 340 this.albumDataSource && this.albumDataSource.unregisterObserver(); 341 } 342 343 this.broadCast = null; 344 } 345 346 getPositionByIndex(index: number): number { 347 if (this.albumDataSource) { 348 return this.albumDataSource.getPositionByIndex(index); 349 } 350 } 351 352 onChange(changeType) { 353 if (this.albumDataSource) { 354 this.albumDataSource.onActive(); 355 this.albumDataSource.onChange(changeType); 356 } 357 } 358 359 switchRefreshOn() { 360 if (this.albumDataSource) { 361 this.albumDataSource.switchRefreshOn(); 362 } 363 }; 364 365 private convertDecodeSize(imageWidth: number, imageHeight: number): number { 366 const HOLD_SCALE: number = 1.0; 367 if (imageWidth <= 0 || imageHeight <= 0) { 368 return HOLD_SCALE; 369 } 370 let displayClass: display.Display = display.getDefaultDisplaySync(); 371 let screenWidth: number = displayClass.width; 372 let screenHeight: number = displayClass.height; 373 if (screenWidth <= 0 || screenHeight <= 0) { 374 return HOLD_SCALE; 375 } 376 let scale = HOLD_SCALE; 377 let desiredScale = screenHeight / screenWidth; 378 let sourceScale = imageHeight / imageWidth; 379 380 if (sourceScale > desiredScale) { 381 scale = screenHeight / imageHeight; 382 } else { 383 scale = screenWidth / imageWidth; 384 } 385 return scale < HOLD_SCALE ? scale : HOLD_SCALE; 386 } 387 388 private generateSampleSize(imageWidth: number, imageHeight: number, index: number): number { 389 let width = vp2px(ScreenManager.getInstance().getWinWidth()); 390 let height = vp2px(ScreenManager.getInstance().getWinHeight()); 391 width = width == 0 ? ScreenManager.DEFAULT_WIDTH : width; 392 height = height == 0 ? ScreenManager.DEFAULT_HEIGHT : height; 393 let maxNumOfPixels 394 if (this.currentIndex == index) { 395 maxNumOfPixels = 2 * width * height; 396 } else { 397 maxNumOfPixels = width * height; 398 } 399 let minSide = Math.min(width, height); 400 return ImageUtil.computeSampleSize(imageWidth, imageHeight, minSide, maxNumOfPixels); 401 } 402}