1/* 2 * Copyright (c) 2023 Shenzhen Kaihong Digital Industry Development 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 deviceInfo from '@ohos.deviceInfo'; 17import window from '@ohos.window'; 18import { stashOrGetObject } from '../utils/SingleInstanceUtils'; 19import { Log } from '../utils/Log'; 20import { BroadcastConstants } from '../constants/BroadcastConstants'; 21import { Constants } from '../constants/Constants'; 22import { getResourceString } from '../utils/ResourceUtils'; 23import { GlobalContext } from './GlobalContext'; 24 25export enum ColumnSize { 26 COLUMN_TWO = 2, 27 COLUMN_FOUR = 4, 28 COLUMN_SIX = 6, 29 COLUMN_EIGHT = 8, 30 COLUMN_TWELVE = 12 31} 32 33enum ScreenWidth { 34 WIDTH_MEDIUM = 520, 35 WIDTH_LARGE = 840 36} 37 38enum WindowMode { 39 UNDEFINED = 1, 40 FULL_SCREEN, 41 PRIMARY, 42 SECONDARY, 43 FLOATING 44} 45 46interface Size { 47 width: number; 48 height: number; 49} 50 51interface StatusNaviHeight { 52 status: number; 53 navi: number; 54} 55 56interface GetWindowMode { 57 getWindowMode(): Promise<WindowMode>; 58} 59 60const TAG = 'ScreenManager'; 61 62const APP_KEY_SCREEN_MANAGER = 'app_key_screen_manager'; 63 64const SCREEN_ORIENTATION_HORIZONTAL: string = 'isHorizontal'; 65const SCREEN_SIDEBAR: string = 'isSidebar'; 66 67const COLUMN_MARGIN: number = 12; 68const COLUMN_GUTTER: number = 12; 69 70type CallbackType = Function; 71 72class ScreenManager { 73 readonly ON_WIN_SIZE_CHANGED = 'on_win_size_changed'; 74 readonly ON_LEFT_BLANK_CHANGED = 'on_left_blank_changed'; 75 readonly DEFAULT_WIDTH: number = 1920; 76 readonly DEFAULT_HEIGHT: number = 1080; 77 readonly SPLIT_THRESHOLD = 1.7; 78 private winWidth = 0.0; 79 private winHeight = 0.0; 80 private statusBarHeight = 0; 81 private naviBarHeight = 0; 82 private leftBlank: number[] = [0, 0, 0, 0]; 83 private events: Map<string, CallbackType[]> = new Map<string, CallbackType[]>(); 84 private mainWindow: window.Window = undefined; 85 private globalThis = GlobalContext.getContext(); 86 87 // Default orientation 88 private horizontal = deviceInfo.deviceType === 'phone' || deviceInfo.deviceType === 'default' ? false : true; 89 90 // Default sidebar 91 private sidebar = deviceInfo.deviceType === 'phone' || deviceInfo.deviceType === 'default' ? false : true; 92 private windowMode = WindowMode.UNDEFINED; 93 94 constructor() { 95 Log.info(TAG, 'constructor'); 96 } 97 98 async initializationSize(win): Promise<void> { 99 this.mainWindow = win; 100 this.setMainWindow(win); 101 await this.checkWindowMode(); 102 this.getWindowProperties(win); 103 } 104 105 /** 106 * Add Listeners 107 * 108 * @param event 109 * @param fn 110 */ 111 on(event, fn): void { 112 if (Array.isArray(event)) { 113 for (let i = 0, l = event.length; i < l; i++) { 114 this.on(event[i], fn); 115 } 116 } else { 117 if (this.events.get(event) === null || this.events.get(event) === undefined) { 118 this.events.set(event, []); 119 } 120 this.events.get(event).push(fn); 121 } 122 } 123 124 /** 125 * Delete Listeners 126 * 127 * @param event 128 * @param fn 129 */ 130 off(event, fn): void { 131 if (event == null || event == undefined) { 132 return; 133 } 134 if (Array.isArray(event)) { 135 for (let i = 0, l = event.length; i < l; i++) { 136 this.off(event[i], fn); 137 } 138 } 139 const cbs: CallbackType[] = this.events.get(event); 140 if (!new Boolean(cbs).valueOf()) { 141 return; 142 } 143 if (fn == null || fn == undefined) { 144 return; 145 } 146 let cb; 147 let i = cbs.length; 148 while (i-- > 0) { 149 cb = cbs[i]; 150 if (cb === fn || cb.fn === fn) { 151 cbs.splice(i, 1); 152 break; 153 } 154 } 155 } 156 157 private emit(event, argument: Object[]): void { 158 let _self = this; 159 if (!new Boolean(this.events.get(event)).valueOf()) { 160 return; 161 } 162 163 let cbs: CallbackType[] = []; 164 for (let i = 0; i < this.events.get(event).length; i++) { 165 cbs.push(this.events.get(event)[i]); 166 } 167 168 if (cbs.length > 0) { 169 for (let i = 0, l = cbs.length; i < l; i++) { 170 let ref = cbs[i]; 171 if (ref != null) { 172 try { 173 ref.apply(_self, argument); 174 } catch (e) { 175 new Error(e); 176 } 177 } 178 } 179 } 180 } 181 182 private isLeftBlankInitialized(): boolean { 183 return this.leftBlank[0] !== 0 || this.leftBlank[1] !== 0 || this.leftBlank[2] !== 0 || this.leftBlank[3] !== 0; 184 } 185 186 // Unit:vp 187 getWinWidth(): number { 188 return px2vp(this.winWidth); 189 } 190 191 setWinWidth(width: number): void { 192 this.winWidth = width; 193 } 194 195 // Unit:vp 196 getWinHeight(): number { 197 return px2vp(this.winHeight); 198 } 199 200 getStatusBarHeight(): number { 201 return px2vp(this.statusBarHeight); 202 } 203 204 getNaviBarHeight(): number { 205 return px2vp(this.naviBarHeight); 206 } 207 208 async initWindowMode(): Promise<void> { 209 Log.debug(TAG, 'start to initialize photos application window mode: ' + this.windowMode); 210 } 211 212 isSplitMode(): boolean { 213 return (WindowMode.PRIMARY === this.windowMode || WindowMode.SECONDARY === this.windowMode) 214 } 215 216 async checkWindowMode(): Promise<void> { 217 let before = this.windowMode; 218 let photosWindowStage = this.globalThis.getObject('photosWindowStage') as GetWindowMode; 219 let mode = await photosWindowStage.getWindowMode(); 220 Log.info(TAG, 'photos application before/current window mode: ' + before + '/' + mode); 221 222 if (before === mode) { 223 return; 224 } 225 this.windowMode = mode; 226 227 if (WindowMode.FULL_SCREEN == this.windowMode) { 228 this.setFullScreen(); 229 } else { 230 this.setSplitScreen(); 231 } 232 } 233 234 private setMainWindow(win: window.Window): void { 235 Log.debug(TAG, 'setMainWindow'); 236 win.on('windowSizeChange', (data: window.Size): void => { 237 Log.debug(TAG, 'windowSizeChange ' + JSON.stringify(data)); 238 this.checkWindowMode(); 239 this.onWinSizeChanged(data); 240 }) 241 } 242 243 private getWindowProperties(win: window.Window): void { 244 Log.debug(TAG, 'getWindowProperties'); 245 try { 246 let properties = win.getWindowProperties(); 247 if (properties.windowRect.width !== 0 && properties.windowRect.height !== 0) { 248 this.winWidth = properties.windowRect.width; 249 this.winHeight = properties.windowRect.height; 250 } 251 Log.debug(TAG, 'this.winWidth = ' + this.winWidth + ' this.winHeight = ' + this.winHeight); 252 } catch (exception) { 253 console.error('Failed to obtain the window properties. Cause: ' + JSON.stringify(exception)); 254 } 255 } 256 257 private async setFullScreen(): Promise<void> { 258 let topWindow: window.Window = AppStorage.Get<window.Window>(Constants.MAIN_WINDOW); 259 Log.debug(TAG, 'getTopWindow start'); 260 try { 261 await topWindow.setWindowLayoutFullScreen(true); 262 Log.debug(TAG, 'setFullScreen true Succeeded'); 263 await this.hideStatusBar(topWindow); 264 } catch (err) { 265 Log.error(TAG, 'setFullScreen err: ' + err); 266 } 267 } 268 269 setSplitScreen(): void { 270 try { 271 this.statusBarHeight = 0; 272 this.naviBarHeight = 0; 273 this.leftBlank = [0, 0, 0, 0]; 274 AppStorage.SetOrCreate<number[]>(BroadcastConstants.LEFT_BLANK, this.leftBlank); 275 } catch (err) { 276 Log.error(TAG, 'setSplitScreen err: ' + err); 277 } 278 } 279 280 private async hideStatusBar(topWindow: window.Window): Promise<void> { 281 Log.debug(TAG, 'hideStatusBar start'); 282 let names: string[] = ['navigation']; 283 Log.debug(TAG, 'getTopWindow names: ' + names + ' end'); 284 try { 285 // @ts-ignore 286 await topWindow.setWindowSystemBarEnable(names); 287 Log.debug(TAG, 'hideStatusBar Succeeded'); 288 let data = await topWindow.getWindowAvoidArea(0) 289 Log.debug(TAG, 'Succeeded in obtaining the area. Data: ' + JSON.stringify(data)); 290 this.onLeftBlankChanged(data); 291 let barColor: string = await getResourceString($r('app.color.default_background_color')); 292 let barContentColor: string = await getResourceString($r('app.color.default_bar_content_color')); 293 if (!barColor) { 294 barColor = '#FFF1F3F5'; 295 } 296 if (!barContentColor) { 297 barContentColor = '#FF000000'; 298 } 299 let systemBarProperties: window.SystemBarProperties = { 300 navigationBarColor: barColor, 301 navigationBarContentColor: barContentColor 302 }; 303 await topWindow.setWindowSystemBarProperties(systemBarProperties); 304 Log.info(TAG, 'setStatusBarColor done'); 305 } catch (err) { 306 Log.error(TAG, 'hideStatusBar err: ' + err); 307 } 308 } 309 310 async setNavigationBarColor(barColor: string, barContentColor: string): Promise<void> { 311 Log.debug(TAG, 'setNavigationBarColor start'); 312 let topWindow: window.Window = AppStorage.Get<window.Window>(Constants.MAIN_WINDOW); 313 try { 314 let systemBarProperties: window.SystemBarProperties = { 315 navigationBarColor: barColor, 316 navigationBarContentColor: barContentColor 317 }; 318 topWindow.setWindowSystemBarProperties( 319 systemBarProperties, 320 (): void => Log.info(TAG, 'setStatusBarColor done') 321 ); 322 } catch (err) { 323 Log.error(TAG, 'setNavigationBarColor err: ' + err); 324 } 325 } 326 327 setSystemUi(isShowBar: boolean): void { 328 Log.debug(TAG, 'setSystemUi start'); 329 let topWindow: window.Window = AppStorage.Get<window.Window>(Constants.MAIN_WINDOW); 330 Log.debug(TAG, 'getTopWindow start'); 331 let names: string[] = ['navigation']; 332 if (!isShowBar) { 333 names = []; 334 } 335 Log.debug(TAG, 'getTopWindow names: ' + names + ' end'); 336 try { 337 // @ts-ignore 338 topWindow.setWindowSystemBarEnable(names, async (): Promise<void> => { 339 Log.debug(TAG, 'setFullScreen Succeeded'); 340 if (isShowBar) { 341 let data = await topWindow.getWindowAvoidArea(0); 342 this.onLeftBlankChanged(data); 343 } 344 }) 345 } catch (err) { 346 Log.error(TAG, 'setSystemUi err: ' + err); 347 } 348 } 349 350 private onLeftBlankChanged(area: window.AvoidArea): void { 351 if (area == null || area == undefined || area.bottomRect.height === 0) { 352 return; 353 } 354 let leftBlankBefore: StatusNaviHeight = { 355 status: this.statusBarHeight, 356 navi: this.naviBarHeight 357 }; 358 this.statusBarHeight = 0; 359 this.naviBarHeight = area.bottomRect.height; 360 this.leftBlank = [this.leftBlank[0], this.leftBlank[1], this.leftBlank[2], area.bottomRect.height]; 361 if (leftBlankBefore.status !== this.statusBarHeight || leftBlankBefore.navi !== this.naviBarHeight) { 362 Log.info(TAG, 'leftBlank changed: ' + JSON.stringify(leftBlankBefore) + '-' + JSON.stringify(this.leftBlank)) 363 AppStorage.SetOrCreate<number[]>(BroadcastConstants.LEFT_BLANK, this.leftBlank); 364 } 365 } 366 367 private onWinSizeChanged(size): void { 368 Log.info(TAG, 'onWinSizeChanged ' + JSON.stringify(size)); 369 if (size == null || size == undefined) { 370 return; 371 } 372 let sizeBefore: Size = { 373 width: this.winWidth, 374 height: this.winHeight 375 }; 376 this.winWidth = size.width; 377 this.winHeight = size.height; 378 379 380 if (sizeBefore.width !== this.winWidth || sizeBefore.height !== this.winHeight) { 381 Log.info(TAG, 'winSize changed: ' + JSON.stringify(sizeBefore) + ' -> ' + JSON.stringify(size)); 382 this.emit(screenManager.ON_WIN_SIZE_CHANGED, [size]); 383 } 384 } 385 386 private onRotationAngleChanged(angle): void { 387 if (angle == null || angle == undefined) { 388 return; 389 } 390 391 if (angle === 0) { 392 this.horizontal = false; 393 } else { 394 this.horizontal = true; 395 } 396 AppStorage.SetOrCreate<boolean>(SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 397 } 398 399 isHorizontal(): boolean { 400 if (AppStorage.Get<boolean>(SCREEN_ORIENTATION_HORIZONTAL) == null) { 401 AppStorage.SetOrCreate<boolean>(SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 402 } 403 return AppStorage.Get<boolean>(SCREEN_ORIENTATION_HORIZONTAL); 404 } 405 406 isSidebar(): boolean { 407 if (AppStorage.Get<boolean>(SCREEN_SIDEBAR) == null) { 408 AppStorage.SetOrCreate<boolean>(SCREEN_SIDEBAR, this.sidebar); 409 } 410 return AppStorage.Get<boolean>(SCREEN_SIDEBAR); 411 } 412 413 getColumnsWidth(count: number): number { 414 let columns = this.getScreenColumns(); 415 Log.info(TAG, 'getColumnsWidth count is ' + count + ' columns: ' + columns); 416 let columnWidth = (px2vp(this.winWidth) - COLUMN_MARGIN) / columns; 417 return columnWidth * count - COLUMN_GUTTER; 418 } 419 420 getScreenColumns(): number { 421 let width = px2vp(this.winWidth); 422 if (width < ScreenWidth.WIDTH_MEDIUM) { 423 return ColumnSize.COLUMN_FOUR; 424 } else if (width >= ScreenWidth.WIDTH_MEDIUM && width < ScreenWidth.WIDTH_LARGE) { 425 return ColumnSize.COLUMN_EIGHT; 426 } else { 427 return ColumnSize.COLUMN_TWELVE; 428 } 429 } 430 431 setKeepScreenOn(): void { 432 Log.info(TAG, 'setKeepScreenOn start'); 433 let topWindow: window.Window = AppStorage.Get<window.Window>('mainWindow'); 434 try { 435 topWindow.setWindowKeepScreenOn(true, (): void => Log.info(TAG, 'setKeepScreenOn Succeeded')) 436 } catch (err) { 437 Log.error(TAG, 'setKeepScreenOn err: ' + err); 438 } 439 } 440 441 setKeepScreenOff(): void { 442 Log.info(TAG, 'setKeepScreenOff start'); 443 let topWindow: window.Window = AppStorage.Get<window.Window>('mainWindow'); 444 try { 445 topWindow.setWindowKeepScreenOn(false, (): void => Log.info(TAG, 'setKeepScreenOff Succeeded')) 446 } catch (err) { 447 Log.error(TAG, 'setKeepScreenOff err: ' + err); 448 } 449 } 450} 451 452export let screenManager: ScreenManager = stashOrGetObject<ScreenManager>(new ScreenManager(), TAG); 453