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 this.getHost()?.off('windowSizeChange', (data: window.Size) => { 247 }); 248 } 249 250 getAvoidArea(): void { 251 if (this.isUIExtensionEnv()) { 252 this.onLeftBlankChanged(this.getHost()?.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)); 253 } else { 254 let topWindow: window.Window = this.getMainWindow(); 255 topWindow?.getAvoidArea(0, (err, data: window.AvoidArea) => { 256 Log.info(TAG, 'Succeeded in obtaining the area. Data:' + JSON.stringify(data)); 257 this.onLeftBlankChanged(data); 258 }); 259 } 260 } 261 262 setFullScreen(): void { 263 if (this.isUIExtensionEnv()) { 264 return; 265 } 266 let topWindow: window.Window = this.getMainWindow(); 267 Log.debug(TAG, 'getTopWindow start'); 268 try { 269 topWindow?.setLayoutFullScreen(true, () => { 270 Log.debug(TAG, 'setFullScreen true Succeeded'); 271 if (AppStorage.get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) { 272 this.hideStatusBar(); 273 } else { 274 this.setWindowBackgroundColorDefault(true); 275 } 276 }); 277 } catch (err) { 278 Log.error(TAG, `setFullScreen err: ${err}`); 279 } 280 } 281 282 setWindowBackgroundColorDefault(defaultColor: boolean): void { 283 if (this.isUIExtensionEnv()) { 284 return; 285 } 286 this.getMainWindow()?.setWindowBackgroundColor(defaultColor ? '#F1F3F5' : '#000000'); 287 } 288 289 setSplitScreen(): void { 290 try { 291 this.statusBarHeight = 0; 292 this.naviBarHeight = 0; 293 this.leftBlank = [0, 0, 0, 0]; 294 this.emit(ScreenManager.ON_LEFT_BLANK_CHANGED, [this.leftBlank]); 295 } catch (err) { 296 Log.error(TAG, `setSplitScreen err: ${err}`); 297 } 298 } 299 300 hideStatusBar(): void { 301 if (this.isUIExtensionEnv()) { 302 return; 303 } 304 Log.debug(TAG, 'hideStatusBar start'); 305 let topWindow: window.Window = this.getMainWindow(); 306 Log.debug(TAG, 'getTopWindow start'); 307 let names: Array<SystemBarKeys> = new Array<SystemBarKeys>('navigation'); 308 Log.debug(TAG, `getTopWindow names: ${names} end`); 309 try { 310 topWindow.setSystemBarEnable(names, () => { 311 Log.debug(TAG, 'hideStatusBar Succeeded'); 312 topWindow?.getAvoidArea(0, async (err, data: window.AvoidArea) => { 313 Log.info(TAG, `Succeeded in obtaining the area. Data: ${JSON.stringify(data)}`); 314 this.onLeftBlankChanged(data); 315 let barColor: string = await UiUtil.getResourceString($r('app.color.transparent')); 316 if (!barColor) { 317 barColor = '#00000000'; 318 } 319 topWindow?.setSystemBarProperties({ navigationBarColor: barColor }, () => { 320 Log.info(TAG, 'setStatusBarColor done'); 321 }); 322 }); 323 }); 324 } catch (err) { 325 Log.error(TAG, `hideStatusBar err: ${err}`); 326 } 327 } 328 329 async setDefaultStatusBarProperties(): Promise<void> { 330 if (this.isUIExtensionEnv()) { 331 return; 332 } 333 Log.debug(TAG, 'setStatusBarColor start'); 334 let topWindow: window.Window = this.getMainWindow(); 335 try { 336 topWindow?.setSystemBarProperties( 337 { statusBarColor: Constants.STATUS_BAR_BACKGROUND_COLOR, 338 statusBarContentColor: Constants.STATUS_BAR_CONTENT_COLOR }, () => { 339 Log.info(TAG, 'setStatusBarColor done'); 340 }); 341 } catch (err) { 342 Log.error(TAG, `setStatusBarColor err: ${err}`); 343 } 344 } 345 346 setSystemUi(isShowBar: boolean): void { 347 if (this.isUIExtensionEnv()) { 348 return; 349 } 350 let deviceTp: string = AppStorage.get('deviceType') as string; 351 Log.debug(TAG, `setSystemUi start, isShowBar=${isShowBar}, deviceType=${deviceTp}`); 352 let topWindow: window.Window = this.getMainWindow(); 353 Log.debug(TAG, 'getTopWindow start'); 354 let names: Array<SystemBarKeys> = new Array<SystemBarKeys>('status', 'navigation'); 355 if (deviceTp === Constants.PC_DEVICE_TYPE || deviceTp === Constants.PAD_DEVICE_TYPE) { 356 names = new Array<SystemBarKeys>('navigation'); 357 } 358 if (!isShowBar) { 359 names = []; 360 } 361 Log.debug(TAG, `getTopWindow names: ${names} end`); 362 try { 363 topWindow?.setSystemBarEnable(names, () => { 364 Log.debug(TAG, `setSystemUi Succeeded: ${names}`); 365 if (isShowBar) { 366 topWindow.getAvoidArea(0, (err, data: window.AvoidArea) => { 367 Log.info(TAG, 'Succeeded in obtaining the area. Data:' + JSON.stringify(data)); 368 this.onLeftBlankChanged(data); 369 }); 370 } 371 }) 372 } catch (err) { 373 Log.error(TAG, `setSystemUi err: ${err}`); 374 } 375 } 376 377 isHorizontal(): boolean { 378 if (AppStorage.get(Constants.SCREEN_ORIENTATION_HORIZONTAL) == null) { 379 AppStorage.SetOrCreate(Constants.SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 380 } 381 return AppStorage.get(Constants.SCREEN_ORIENTATION_HORIZONTAL); 382 } 383 384 isSidebar(): boolean { 385 if (AppStorage.get(Constants.SCREEN_SIDEBAR) == null) { 386 AppStorage.SetOrCreate(Constants.SCREEN_SIDEBAR, this.sidebar); 387 } 388 return AppStorage.get(Constants.SCREEN_SIDEBAR); 389 } 390 391 getColumnsWidth(count: number): number { 392 let columnWidth: number = (this.winWidth - Constants.COLUMN_MARGIN) / this.columns; 393 columnWidth = parseInt((columnWidth * count - Constants.COLUMN_GUTTER) + ''); 394 Log.info(TAG, `getColumnsWidth count is ${count} colunms is ${this.columns}, columnWidth is ${columnWidth} `); 395 return columnWidth; 396 } 397 398 getScreenColumns(): number { 399 return this.columns; 400 } 401 402 onRotationAngleChanged(isH: boolean): void { 403 Log.info(TAG, `onRotationAngleChanged horizontal: ${isH}`); 404 if (isH === null || isH === undefined) { 405 return; 406 } 407 this.horizontal = isH; 408 AppStorage.SetOrCreate(Constants.SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 409 } 410 411 private getMainWindow(): window.Window { 412 return AppStorage.get<window.Window>('mainWindow'); 413 } 414 415 private getProxy(): uiExtensionHost.UIExtensionHostWindowProxy { 416 return AppStorage.get<uiExtensionHost.UIExtensionHostWindowProxy>(Constants.PHOTO_PICKER_EXTENSION_WINDOW) as uiExtensionHost.UIExtensionHostWindowProxy; 417 } 418 419 private emit(event: string, argument: unknown[]): void { 420 this.broadcast.emit(event, argument); 421 } 422 423 private isLeftBlankInitialized(): boolean { 424 return this.leftBlank[0] != 0 || this.leftBlank[1] != 0 || this.leftBlank[2] != 0 || this.leftBlank[3] != 0; 425 } 426 427 private onLeftBlankChanged(area: window.AvoidArea): void { 428 if (area === null || area === undefined || (area.bottomRect.height === 0 && area.topRect.height === 0)) { 429 return; 430 } 431 let leftBlankBefore = { 432 status: this.statusBarHeight, 433 navi: this.naviBarHeight 434 }; 435 // Area data obtained through the system interface, 436 // There is a possibility that the area data is incorrect. 437 AppStorage.SetOrCreate<number>('statusBarHeight', area.topRect.height); 438 this.statusBarHeight = px2vp(area.topRect.height); 439 this.naviBarHeight = px2vp(area.bottomRect.height); 440 this.leftBlank = [this.leftBlank[0], this.leftBlank[1], this.leftBlank[2], px2vp(area.bottomRect.height)]; 441 if (leftBlankBefore.status != this.statusBarHeight || leftBlankBefore.navi != this.naviBarHeight) { 442 Log.info(TAG, `leftBlank changed: ${JSON.stringify(leftBlankBefore)}-${JSON.stringify(this.leftBlank)}`); 443 this.emit(ScreenManager.ON_LEFT_BLANK_CHANGED, [this.leftBlank]); 444 } 445 } 446 447 private onWinSizeChanged(size: window.Size | window.Rect): void { 448 Log.info(TAG, `onWinSizeChanged ${JSON.stringify(size)}`); 449 if (size == null || size == undefined) { 450 return; 451 } 452 let isSplitModeBefore: boolean = this.isSplitMode(); 453 this.checkWindowMode(); 454 let sizeBefore = { 455 width: this.winWidth, 456 height: this.winHeight 457 }; 458 this.winWidth = px2vp(size.width); 459 this.winHeight = px2vp(size.height); 460 Log.info(TAG, `onChanged winSize: ${size.width}*${size.height} px, ${this.winWidth}*${this.winHeight} vp`); 461 if (this.winWidth < ScreenWidth.WIDTH_MEDIUM) { 462 this.columns = ColumnSize.COLUMN_FOUR; 463 } else if (this.winWidth >= ScreenWidth.WIDTH_MEDIUM && this.winWidth < ScreenWidth.WIDTH_LARGE) { 464 this.columns = ColumnSize.COLUMN_EIGHT; 465 } else { 466 this.columns = ColumnSize.COLUMN_TWELVE; 467 } 468 let isSplitModeNow: boolean = this.isSplitMode(); 469 if (isSplitModeBefore != isSplitModeNow) { 470 Log.info(TAG, `splitMode changed: ${isSplitModeBefore} -> ${isSplitModeNow}`); 471 this.emit(ScreenManager.ON_SPLIT_MODE_CHANGED, [isSplitModeNow]); 472 } 473 if (sizeBefore.width != this.winWidth || sizeBefore.height != this.winHeight) { 474 let newSize = { 475 width: this.winWidth, 476 height: this.winHeight 477 }; 478 Log.info(TAG, `winSize changed: ${JSON.stringify(sizeBefore)} -> ${JSON.stringify(newSize)}`); 479 this.emit(ScreenManager.ON_WIN_SIZE_CHANGED, [size]); 480 } 481 } 482 483 isUIExtensionEnv(): boolean { 484 let uiExtensionStage: uiExtensionHost.UIExtensionHostWindowProxy = this.getProxy(); 485 return uiExtensionStage ? true : false; 486 } 487 488 private getHost(): uiExtensionHost.UIExtensionHostWindowProxy | window.Window { 489 if (this.isUIExtensionEnv()) { 490 return this.getProxy(); 491 } else { 492 return this.getMainWindow(); 493 } 494 } 495 496 private getWinRect(): window.Rect { 497 if (this.isUIExtensionEnv()) { 498 return this.getProxy()?.properties?.uiExtensionHostWindowProxyRect; 499 } 500 return this.getMainWindow()?.getWindowProperties()?.windowRect; 501 } 502} 503