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 { Constants } from './Constants'; 18import { UiUtil } from '../../utils/UiUtil'; 19import { BroadCast } from '../../utils/BroadCast'; 20import window from '@ohos.window'; 21import uiExtensionHost from '@ohos.uiExtensionHost'; 22 23const TAG: string = 'common_ScreenManager'; 24 25type SystemBarKeys = 'status' | 'navigation'; 26 27export enum ColumnSize { 28 COLUMN_ONE_POINT_FIVE = 1.5, 29 COLUMN_TWO = 2, 30 COLUMN_FOUR = 4, 31 COLUMN_SIX = 6, 32 COLUMN_EIGHT = 8, 33 COLUMN_TWELVE = 12 34} 35 36enum ScreenWidth { 37 WIDTH_MEDIUM = 520, 38 WIDTH_LARGE = 840 39} 40 41enum WindowMode { 42 UNDEFINED = 1, 43 FULLSCREEN, 44 PRIMARY, 45 SECONDARY, 46 FLOATING 47} 48 49export class ScreenManager { 50 static readonly ON_WIN_SIZE_CHANGED: string = 'on_win_size_changed'; 51 static readonly ON_SPLIT_MODE_CHANGED: string = 'on_split_mode_changed'; 52 static readonly ON_LEFT_BLANK_CHANGED: string = 'on_left_blank_changed'; 53 static readonly DEFAULT_WIDTH: number = 1920; 54 static readonly DEFAULT_HEIGHT: number = 1080; 55 56 // no interface is available to obtain the height of WindowDecor. WindowDecor px Height: 56. 57 static readonly WIN_DECOR_HEIGHT_PX: number = 56; 58 59 private winWidth: number = 0.0; 60 private winHeight: number = 0.0; 61 private statusBarHeight: number = 0; 62 private naviBarHeight: number = 0; 63 private leftBlank: [number, number, number, number] = [0, 0, 0, 0]; 64 private broadcast: BroadCast = new BroadCast(); 65 private mainWindow: window.Window = undefined; 66 private columns: number = ColumnSize.COLUMN_FOUR; 67 68 // Default orientation for Pc 69 private horizontal: boolean = true; 70 71 // Default sidebar for Pc 72 private sidebar: boolean = true; 73 private windowMode: WindowMode = WindowMode.UNDEFINED; 74 private isFullScreen: boolean = true; 75 76 private constructor() { 77 Log.info(TAG, 'constructor'); 78 this.horizontal = false; 79 this.sidebar = false; 80 } 81 82 static getInstance(): ScreenManager { 83 if (AppStorage.get(Constants.APP_KEY_SCREEN_MANAGER) == null) { 84 AppStorage.setOrCreate(Constants.APP_KEY_SCREEN_MANAGER, new ScreenManager()); 85 } 86 let manager: ScreenManager = AppStorage.get(Constants.APP_KEY_SCREEN_MANAGER); 87 return manager; 88 } 89 90 initializationSize(win: window.Window | undefined): Promise<void> { 91 if (win) { 92 this.mainWindow = win; 93 let properties: window.WindowProperties = win.getWindowProperties(); 94 this.isFullScreen = properties.isFullScreen; 95 } 96 let size: window.Rect = this.getWinRect(); 97 98 // Area data obtained through the system interface, 99 // There is a possibility that the area data is incorrect. 100 const statusBarHeight: number = size ? size.top : this.statusBarHeight; 101 AppStorage.setOrCreate<number>('statusBarHeight', statusBarHeight); 102 return new Promise<void>((resolve, reject) => { 103 if (!size) { 104 reject(); 105 } 106 Log.info(TAG, `display screen windowRect: ${JSON.stringify(size)}`); 107 this.winWidth = px2vp(size.width); 108 this.winHeight = px2vp(size.height); 109 Log.info(TAG, `init winSize: ${size.width}*${size.height} px, ${this.winWidth}*${this.winHeight} vp`); 110 if (this.winWidth < ScreenWidth.WIDTH_MEDIUM) { 111 this.columns = ColumnSize.COLUMN_FOUR; 112 } else if (this.winWidth >= ScreenWidth.WIDTH_MEDIUM && this.winWidth < ScreenWidth.WIDTH_LARGE) { 113 this.columns = ColumnSize.COLUMN_EIGHT; 114 } else { 115 this.columns = ColumnSize.COLUMN_TWELVE; 116 } 117 this.emit(ScreenManager.ON_WIN_SIZE_CHANGED, [size]); 118 resolve(); 119 }); 120 } 121 122 /** 123 * Add Listeners 124 * 125 * @param event 126 * @param fn 127 */ 128 on(event: string, fn: Function): void { 129 this.broadcast.on(event, fn); 130 } 131 132 /** 133 * Delete Listeners 134 * 135 * @param event 136 * @param fn 137 */ 138 off(event: string, fn: Function): void { 139 this.broadcast.off(event, fn); 140 } 141 142 setWinWidth(width: number): void { 143 this.winWidth = width; 144 } 145 146 // Unit:vp 147 getWinWidth(): number { 148 return this.winWidth; 149 } 150 151 // Unit:vp 152 getWinHeight(): number { 153 return this.winHeight; 154 } 155 156 // Returns the width of the layout area (LayoutWidth = WindowWidth). 157 getWinLayoutWidth(): number { 158 return this.winWidth; 159 } 160 161 // Returns the height of the layout area (LayoutHeight = WindowHeight - WindowDecorHeight). 162 getWinLayoutHeight(): number { 163 let deviceTp: string = AppStorage.get('deviceType') as string; 164 Log.debug(TAG, `deviceTp=${deviceTp}, isFull=${this.isFullScreen}, winH=${this.winHeight}`); 165 if (deviceTp === Constants.DEFAULT_DEVICE_TYPE) { 166 return this.winHeight; 167 } 168 let winDecorHeight: number = this.isFullScreen ? 0 : px2vp(ScreenManager.WIN_DECOR_HEIGHT_PX); 169 return this.winHeight - winDecorHeight; 170 } 171 172 getStatusBarHeight(): number { 173 return this.statusBarHeight; 174 } 175 176 getNaviBarHeight(): number { 177 return this.naviBarHeight; 178 } 179 180 initWindowMode(): void { 181 Log.debug(TAG, `start to initialize photos application window mode: ${this.windowMode}`); 182 this.checkWindowMode(); 183 this.getHost() && this.setMainWindow(); 184 } 185 186 destroyWindowMode(): void { 187 Log.debug(TAG, `start to destory photos application window mode: ${this.windowMode}`); 188 try { 189 this.getHost()?.off('windowSizeChange', (data: window.Size) => { 190 }); 191 } catch (error) { 192 Log.error(TAG, `destroy window error: ${error}`); 193 } 194 } 195 196 isSplitMode(): boolean { 197 return (WindowMode.PRIMARY === this.windowMode || WindowMode.SECONDARY === this.windowMode); 198 } 199 200 async checkWindowMode(): Promise<void> { 201 if (this.isUIExtensionEnv()) { 202 return; 203 } 204 let before = this.windowMode; 205 let windowStage: window.WindowStage = AppStorage.get<window.WindowStage>('photosWindowStage'); 206 // @ts-ignore 207 let mode: WindowMode = await windowStage?.getWindowMode() as WindowMode; 208 Log.info(TAG, `photos application before/current window mode: ${before}/${mode}`); 209 210 if (before == mode) { 211 return; 212 } 213 this.windowMode = mode; 214 if (WindowMode.FULLSCREEN == this.windowMode) { 215 this.setFullScreen(); 216 } else { 217 this.setSplitScreen(); 218 } 219 } 220 221 setMainWindow(): void { 222 Log.debug(TAG, 'setMainWindow'); 223 this.getHost()?.on('windowSizeChange', (data: window.Size) => { 224 Log.debug(TAG, `windowSizeChange ${JSON.stringify(data)}`); 225 if (!this.isUIExtensionEnv()) { 226 try { 227 let properties: window.WindowProperties = this.mainWindow.getWindowProperties(); 228 this.isFullScreen = properties.isFullScreen; 229 } catch (exception) { 230 Log.error(TAG, 'Failed to obtain the area. Cause:' + JSON.stringify(exception)); 231 } 232 } 233 this.onWinSizeChanged(data); 234 }) 235 if (!this.isUIExtensionEnv()) { 236 this.mainWindow?.getProperties().then((prop: window.WindowProperties) => { 237 Log.info(TAG, `Window prop: ${JSON.stringify(prop)}`); 238 this.onWinSizeChanged(prop.windowRect); 239 }); 240 } else { 241 this.onWinSizeChanged(this.getWinRect()); 242 } 243 } 244 245 destroyMainWindow(): void { 246 try { 247 this.getHost()?.off('windowSizeChange'); 248 } catch (error) { 249 Log.error(TAG, `destroy window error: ${error}, code: ${error?.code}`); 250 } 251 } 252 253 getAvoidArea(): void { 254 if (this.isUIExtensionEnv()) { 255 this.onLeftBlankChanged(this.getHost()?.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)); 256 } else { 257 let topWindow: window.Window = this.getMainWindow(); 258 topWindow?.getAvoidArea(0, (err, data: window.AvoidArea) => { 259 Log.info(TAG, 'Succeeded in obtaining the area. Data:' + JSON.stringify(data)); 260 this.onLeftBlankChanged(data); 261 }); 262 } 263 } 264 265 setFullScreen(): void { 266 if (this.isUIExtensionEnv()) { 267 return; 268 } 269 let topWindow: window.Window = this.getMainWindow(); 270 Log.debug(TAG, 'getTopWindow start'); 271 try { 272 topWindow?.setLayoutFullScreen(true, () => { 273 Log.debug(TAG, 'setFullScreen true Succeeded'); 274 if (AppStorage.get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) { 275 this.hideStatusBar(); 276 } else { 277 this.setWindowBackgroundColorDefault(true); 278 } 279 }); 280 } catch (err) { 281 Log.error(TAG, `setFullScreen err: ${err}`); 282 } 283 } 284 285 setWindowBackgroundColorDefault(defaultColor: boolean): void { 286 if (this.isUIExtensionEnv()) { 287 return; 288 } 289 try { 290 this.getMainWindow()?.setWindowBackgroundColor(defaultColor ? '#F1F3F5' : '#000000'); 291 } catch (error) { 292 Log.error(TAG, 'setWindowBackgroundColorDefault: failed, error info is ' + error + ', code: ' + error?.code); 293 } 294 } 295 296 setSplitScreen(): void { 297 try { 298 this.statusBarHeight = 0; 299 this.naviBarHeight = 0; 300 this.leftBlank = [0, 0, 0, 0]; 301 this.emit(ScreenManager.ON_LEFT_BLANK_CHANGED, [this.leftBlank]); 302 } catch (err) { 303 Log.error(TAG, `setSplitScreen err: ${err}`); 304 } 305 } 306 307 hideStatusBar(): void { 308 if (this.isUIExtensionEnv()) { 309 return; 310 } 311 Log.debug(TAG, 'hideStatusBar start'); 312 let topWindow: window.Window = this.getMainWindow(); 313 Log.debug(TAG, 'getTopWindow start'); 314 let names: Array<SystemBarKeys> = new Array<SystemBarKeys>('navigation'); 315 Log.debug(TAG, `getTopWindow names: ${names} end`); 316 try { 317 topWindow.setSystemBarEnable(names, () => { 318 Log.debug(TAG, 'hideStatusBar Succeeded'); 319 topWindow?.getAvoidArea(0, async (err, data: window.AvoidArea) => { 320 Log.info(TAG, `Succeeded in obtaining the area. Data: ${JSON.stringify(data)}`); 321 this.onLeftBlankChanged(data); 322 let barColor: string = await UiUtil.getResourceString($r('app.color.transparent')); 323 if (!barColor) { 324 barColor = '#00000000'; 325 } 326 topWindow?.setSystemBarProperties({ navigationBarColor: barColor }, () => { 327 Log.info(TAG, 'setStatusBarColor done'); 328 }); 329 }); 330 }); 331 } catch (err) { 332 Log.error(TAG, `hideStatusBar err: ${err}`); 333 } 334 } 335 336 async setDefaultStatusBarProperties(): Promise<void> { 337 if (this.isUIExtensionEnv()) { 338 return; 339 } 340 Log.debug(TAG, 'setStatusBarColor start'); 341 let topWindow: window.Window = this.getMainWindow(); 342 try { 343 topWindow?.setSystemBarProperties( 344 { statusBarColor: Constants.STATUS_BAR_BACKGROUND_COLOR, 345 statusBarContentColor: Constants.STATUS_BAR_CONTENT_COLOR }, () => { 346 Log.info(TAG, 'setStatusBarColor done'); 347 }); 348 } catch (err) { 349 Log.error(TAG, `setStatusBarColor err: ${err}`); 350 } 351 } 352 353 setSystemUi(isShowBar: boolean): void { 354 if (this.isUIExtensionEnv()) { 355 return; 356 } 357 let deviceTp: string = AppStorage.get('deviceType') as string; 358 Log.debug(TAG, `setSystemUi start, isShowBar=${isShowBar}, deviceType=${deviceTp}`); 359 let topWindow: window.Window = this.getMainWindow(); 360 Log.debug(TAG, 'getTopWindow start'); 361 let names: Array<SystemBarKeys> = new Array<SystemBarKeys>('navigation'); 362 if (deviceTp === Constants.PC_DEVICE_TYPE || deviceTp === Constants.PAD_DEVICE_TYPE) { 363 names = new Array<SystemBarKeys>('navigation'); 364 } 365 if (!isShowBar) { 366 names = []; 367 } 368 Log.debug(TAG, `getTopWindow names: ${names} end`); 369 try { 370 topWindow?.setSystemBarEnable(names, () => { 371 Log.debug(TAG, `setSystemUi Succeeded: ${names}`); 372 if (isShowBar) { 373 topWindow.getAvoidArea(0, (err, data: window.AvoidArea) => { 374 Log.info(TAG, 'Succeeded in obtaining the area. Data:' + JSON.stringify(data)); 375 this.onLeftBlankChanged(data); 376 }); 377 } 378 }) 379 } catch (err) { 380 Log.error(TAG, `setSystemUi err: ${err}`); 381 } 382 } 383 384 isHorizontal(): boolean { 385 if (AppStorage.get(Constants.SCREEN_ORIENTATION_HORIZONTAL) == null) { 386 AppStorage.setOrCreate(Constants.SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 387 } 388 return AppStorage.get(Constants.SCREEN_ORIENTATION_HORIZONTAL); 389 } 390 391 isSidebar(): boolean { 392 if (AppStorage.get(Constants.SCREEN_SIDEBAR) == null) { 393 AppStorage.setOrCreate(Constants.SCREEN_SIDEBAR, this.sidebar); 394 } 395 return AppStorage.get(Constants.SCREEN_SIDEBAR); 396 } 397 398 getColumnsWidth(count: number): number { 399 let columnWidth: number = (this.winWidth - Constants.COLUMN_MARGIN) / this.columns; 400 columnWidth = parseInt((columnWidth * count - Constants.COLUMN_GUTTER) + ''); 401 Log.info(TAG, `getColumnsWidth count is ${count} colunms is ${this.columns}, columnWidth is ${columnWidth} `); 402 return columnWidth; 403 } 404 405 getScreenColumns(): number { 406 return this.columns; 407 } 408 409 onRotationAngleChanged(isH: boolean): void { 410 Log.info(TAG, `onRotationAngleChanged horizontal: ${isH}`); 411 if (isH === null || isH === undefined) { 412 return; 413 } 414 this.horizontal = isH; 415 AppStorage.setOrCreate(Constants.SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 416 } 417 418 private getMainWindow(): window.Window { 419 return AppStorage.get<window.Window>('mainWindow'); 420 } 421 422 private getProxy(): uiExtensionHost.UIExtensionHostWindowProxy { 423 return AppStorage.get<uiExtensionHost.UIExtensionHostWindowProxy>(Constants.PHOTO_PICKER_EXTENSION_WINDOW) as uiExtensionHost.UIExtensionHostWindowProxy; 424 } 425 426 private emit(event: string, argument: unknown[]): void { 427 this.broadcast.emit(event, argument); 428 } 429 430 private isLeftBlankInitialized(): boolean { 431 return this.leftBlank[0] != 0 || this.leftBlank[1] != 0 || this.leftBlank[2] != 0 || this.leftBlank[3] != 0; 432 } 433 434 private onLeftBlankChanged(area: window.AvoidArea): void { 435 if (area === null || area === undefined || (area.bottomRect.height === 0 && area.topRect.height === 0)) { 436 return; 437 } 438 let leftBlankBefore = { 439 status: this.statusBarHeight, 440 navi: this.naviBarHeight 441 }; 442 // Area data obtained through the system interface, 443 // There is a possibility that the area data is incorrect. 444 AppStorage.setOrCreate<number>('statusBarHeight', area.topRect.height); 445 this.statusBarHeight = px2vp(area.topRect.height); 446 this.naviBarHeight = px2vp(area.bottomRect.height); 447 this.leftBlank = [this.leftBlank[0], this.leftBlank[1], this.leftBlank[2], px2vp(area.bottomRect.height)]; 448 if (leftBlankBefore.status != this.statusBarHeight || leftBlankBefore.navi != this.naviBarHeight) { 449 Log.info(TAG, `leftBlank changed: ${JSON.stringify(leftBlankBefore)}-${JSON.stringify(this.leftBlank)}`); 450 this.emit(ScreenManager.ON_LEFT_BLANK_CHANGED, [this.leftBlank]); 451 } 452 } 453 454 private onWinSizeChanged(size: window.Size | window.Rect): void { 455 Log.info(TAG, `onWinSizeChanged ${JSON.stringify(size)}`); 456 if (size == null || size == undefined) { 457 return; 458 } 459 let isSplitModeBefore: boolean = this.isSplitMode(); 460 this.checkWindowMode(); 461 let sizeBefore = { 462 width: this.winWidth, 463 height: this.winHeight 464 }; 465 this.winWidth = px2vp(size.width); 466 this.winHeight = px2vp(size.height); 467 Log.info(TAG, `onChanged winSize: ${size.width}*${size.height} px, ${this.winWidth}*${this.winHeight} vp`); 468 if (this.winWidth < ScreenWidth.WIDTH_MEDIUM) { 469 this.columns = ColumnSize.COLUMN_FOUR; 470 } else if (this.winWidth >= ScreenWidth.WIDTH_MEDIUM && this.winWidth < ScreenWidth.WIDTH_LARGE) { 471 this.columns = ColumnSize.COLUMN_EIGHT; 472 } else { 473 this.columns = ColumnSize.COLUMN_TWELVE; 474 } 475 let isSplitModeNow: boolean = this.isSplitMode(); 476 if (isSplitModeBefore != isSplitModeNow) { 477 Log.info(TAG, `splitMode changed: ${isSplitModeBefore} -> ${isSplitModeNow}`); 478 this.emit(ScreenManager.ON_SPLIT_MODE_CHANGED, [isSplitModeNow]); 479 } 480 if (sizeBefore.width != this.winWidth || sizeBefore.height != this.winHeight) { 481 let newSize = { 482 width: this.winWidth, 483 height: this.winHeight 484 }; 485 Log.info(TAG, `winSize changed: ${JSON.stringify(sizeBefore)} -> ${JSON.stringify(newSize)}`); 486 this.emit(ScreenManager.ON_WIN_SIZE_CHANGED, [size]); 487 } 488 } 489 490 isUIExtensionEnv(): boolean { 491 let uiExtensionStage: uiExtensionHost.UIExtensionHostWindowProxy = this.getProxy(); 492 return uiExtensionStage ? true : false; 493 } 494 495 private getHost(): uiExtensionHost.UIExtensionHostWindowProxy | window.Window { 496 if (this.isUIExtensionEnv()) { 497 return this.getProxy(); 498 } else { 499 return this.getMainWindow(); 500 } 501 } 502 503 private getWinRect(): window.Rect { 504 if (this.isUIExtensionEnv()) { 505 return this.getProxy()?.properties?.uiExtensionHostWindowProxyRect; 506 } 507 return this.getMainWindow()?.getWindowProperties()?.windowRect; 508 } 509} 510