• 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    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