• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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