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 setBroadCast(broadCastParam: BroadCast): void { 242 this.broadCast = broadCastParam; 243 } 244 245 setBroadCastToAlbum(broadCastParam: BroadCast) { 246 if (this.albumDataSource) { 247 this.albumDataSource.setPhotoBroadCast(broadCastParam); 248 } 249 } 250 251 onDataReloaded() { 252 Log.info(TAG, `onDataReloaded start`); 253 if (this.albumDataSource) { 254 this.albumDataSource.onDataReloaded(); 255 } 256 } 257 258 onSizeLoadingFinished(size: number): void { 259 Log.info(TAG, `onSizeLoadingFinished, current size: ${size}`); 260 this.broadCast && this.broadCast.emit(PhotoConstants.DATA_SIZE_CHANGED, [size]); 261 } 262 263 onDataLoadingFinished(): void { 264 // after the mediaLib updates the data, it directly calls onDataReloaded. 265 // swiper updates only the five mounted items 266 Log.debug(TAG, `onDataLoadingFinished listeners size: ${this.albumDataSource.listeners.length}\ 267 totalCount: ${this.totalCount()}`); 268 this.broadCast && this.broadCast.emit(PhotoConstants.DATA_CONTENT_CHANGED, []); 269 } 270 271 onDataChanged(dataIndex: number): void { 272 if (this.albumDataSource) { 273 this.albumDataSource.listeners.forEach(listener => { 274 listener.onDataChanged(dataIndex); 275 }) 276 } 277 } 278 279 deleteData(index: number) { 280 if (this.albumDataSource) { 281 this.albumDataSource.listeners.forEach(listener => { 282 Log.debug(TAG, 'onDataDeleted'); 283 listener.onDataDeleted(index); 284 }) 285 } 286 } 287 288 async getDataByUri(uri: string): Promise<FileAsset> { 289 let tmp: FileAsset = await this.photoDataImpl.getDataByUri(uri) as FileAsset; 290 return tmp; 291 } 292 293 getItemByUri(uri: string): any { 294 if (this.albumDataSource) { 295 return this.albumDataSource.getItemByUri(uri); 296 } 297 return null; 298 } 299 300 getDataIndexByUri(uri: string): number { 301 if (this.albumDataSource) { 302 return this.albumDataSource.getDataIndexByUri(uri); 303 } 304 return Constants.NOT_FOUND; 305 } 306 // Search for the index of first valid item in MediaDataSource 307 getValidStartIndex(): number { 308 if (this.albumDataSource) { 309 return this.albumDataSource.getValidStartIndex(); 310 } 311 return Constants.NOT_FOUND; 312 } 313 // Search for the index of last valid item in MediaDataSource 314 getValidEndIndex(): number { 315 if (this.albumDataSource) { 316 return this.albumDataSource.getValidEndIndex(); 317 } 318 return Constants.NOT_FOUND; 319 } 320 321 public release(): void { 322 if (this.albumDataSource) { 323 this.albumDataSource.releasePhotoBroadCast(); 324 this.albumDataSource.removeLoadingListener(this); 325 // cancel the mediaLibrary listening of albumDataSource when the large image is destroyed. 326 // cannot cancel it in unregisterDataChangeListener 327 this.albumDataSource && this.albumDataSource.unregisterObserver(); 328 } 329 330 this.broadCast = null; 331 } 332 333 getPositionByIndex(index: number): number { 334 if (this.albumDataSource) { 335 return this.albumDataSource.getPositionByIndex(index); 336 } 337 } 338 339 onChange(changeType) { 340 if (this.albumDataSource) { 341 this.albumDataSource.onActive(); 342 this.albumDataSource.onChange(changeType); 343 } 344 } 345 346 switchRefreshOn() { 347 if (this.albumDataSource) { 348 this.albumDataSource.switchRefreshOn(); 349 } 350 }; 351 352 private convertDecodeSize(imageWidth: number, imageHeight: number): number { 353 const HOLD_SCALE: number = 1.0; 354 if (imageWidth <= 0 || imageHeight <= 0) { 355 return HOLD_SCALE; 356 } 357 let displayClass: display.Display = display.getDefaultDisplaySync(); 358 let screenWidth: number = displayClass.width; 359 let screenHeight: number = displayClass.height; 360 if (screenWidth <= 0 || screenHeight <= 0) { 361 return HOLD_SCALE; 362 } 363 let scale = HOLD_SCALE; 364 let desiredScale = screenHeight / screenWidth; 365 let sourceScale = imageHeight / imageWidth; 366 367 if (sourceScale > desiredScale) { 368 scale = screenHeight / imageHeight; 369 } else { 370 scale = screenWidth / imageWidth; 371 } 372 return scale < HOLD_SCALE ? scale : HOLD_SCALE; 373 } 374 375 private generateSampleSize(imageWidth: number, imageHeight: number, index: number): number { 376 let width = vp2px(ScreenManager.getInstance().getWinWidth()); 377 let height = vp2px(ScreenManager.getInstance().getWinHeight()); 378 width = width == 0 ? ScreenManager.DEFAULT_WIDTH : width; 379 height = height == 0 ? ScreenManager.DEFAULT_HEIGHT : height; 380 let maxNumOfPixels 381 if (this.currentIndex == index) { 382 maxNumOfPixels = 2 * width * height; 383 } else { 384 maxNumOfPixels = width * height; 385 } 386 let minSide = Math.min(width, height); 387 return ImageUtil.computeSampleSize(imageWidth, imageHeight, minSide, maxNumOfPixels); 388 } 389}