1/**
2 * Copyright (c) 2021-2022 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 window from '@ohos.window';
17import display from '@ohos.display';
18import commonEventMgr from '@ohos.commonEventManager';
19import common from '@ohos.app.ability.common';
20import { AsyncCallback, BusinessError } from '@ohos.base';
21import AbilityConstant from '@ohos.app.ability.AbilityConstant';
22import commonEventManager from './CommonEventManager'
23import { Log } from '../utils/Log';
24import { StyleConstants } from '../constants/StyleConstants';
25
26const TAG = 'WindowManager';
27
28/**
29 * Wrapper class for window interfaces.
30 */
31class WindowManager {
32  private mDisplayData: display.Display | null = null;
33
34  private static subscriber: commonEventMgr.CommonEventSubscriber;
35
36  private static eventCallback: AsyncCallback<commonEventMgr.CommonEventData>;
37
38  RECENT_WINDOW_NAME = 'RecentView';
39
40  DESKTOP_WINDOW_NAME = 'EntryView';
41
42  APP_CENTER_WINDOW_NAME = 'AppCenterView';
43
44  FORM_MANAGER_WINDOW_NAME = 'FormManagerView';
45
46  FORM_SERVICE_WINDOW_NAME = 'FormServiceView';
47
48  DESKTOP_RANK = window.WindowType.TYPE_DESKTOP;
49
50  RECENT_RANK = window.WindowType.TYPE_LAUNCHER_RECENT;
51
52  DOCK_RANK = window.WindowType.TYPE_LAUNCHER_DOCK;
53
54  recentMode?: number;
55
56  /**
57   * get WindowManager instance
58   *
59   * @return WindowManager singleton
60   */
61  static getInstance(): WindowManager {
62    if (globalThis.WindowManager == null) {
63      globalThis.WindowManager = new WindowManager();
64      this.eventCallback = this.winEventCallback.bind(this);
65      this.initSubscriber();
66    }
67    return globalThis.WindowManager;
68  }
69
70  /**
71   * get window width
72   *
73   * @return windowWidth
74   */
75  getWindowWidth(): number {
76    if (this.mDisplayData == null) {
77      this.mDisplayData = this.getWindowDisplayData();
78    }
79    return this.mDisplayData?.width as number;
80  }
81
82  /**
83   * get window height
84   *
85   * @return windowHeight
86   */
87  getWindowHeight(): number {
88    if (this.mDisplayData == null) {
89      this.mDisplayData = this.getWindowDisplayData();
90    }
91    return this.mDisplayData?.height as number;
92  }
93
94  private getWindowDisplayData(): display.Display | null {
95    let displayData: display.Display | null = null;
96    try {
97      displayData = display.getDefaultDisplaySync();
98    } catch(err) {
99      Log.showError(TAG, `display.getDefaultDisplaySync error: ${JSON.stringify(err)}`);
100    }
101    return displayData;
102  }
103
104  isSplitWindowMode(mode): boolean {
105    if ((mode === AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_PRIMARY) ||
106    (mode === AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_SECONDARY)) {
107      return true;
108    }
109    return false;
110  }
111
112  /**
113   * set window size
114   *
115   * @param width window width
116   * @param height window height
117   */
118  async setWindowSize(width: number, height: number): Promise<void> {
119    const abilityWindow = await window.getLastWindow(globalThis.desktopContext as common.BaseContext);
120    void abilityWindow.resize(width, height);
121  }
122
123  /**
124   * set window position
125   *
126   * @param x coordinate x
127   * @param y coordinate y
128   */
129  async setWindowPosition(x: number, y: number): Promise<void> {
130    const abilityWindow = await window.getLastWindow(globalThis.desktopContext as common.BaseContext);
131    void abilityWindow.moveWindowTo(x, y);
132  }
133
134  /**
135   * 隐藏状态栏
136   *
137   * @param name windowName
138   */
139  hideWindowStatusBar(name: string) {
140    let names: Array<'status'|'navigation'> = ['navigation'];
141    this.setWindowSystemBar(name, names);
142  }
143
144  /**
145   * 显示状态栏
146   *
147   * @param name
148   */
149  showWindowStatusBar(name: string) {
150    let names: Array<'status'|'navigation'> = ['navigation', 'status'];
151    this.setWindowSystemBar(name, names);
152  }
153
154  /**
155   * 设置状态栏与导航栏显隐
156   *
157   * @param windowName
158   * @param names 值为 'status'|'navigation' 枚举
159   */
160  private setWindowSystemBar(windowName: string, names: Array<'status'|'navigation'>) {
161    this.findWindow(windowName, win => {
162      win.setWindowSystemBarEnable(names).then(() => {
163        Log.showInfo(TAG, 'set statusBar success');
164      }).catch(err => {
165        Log.showInfo(TAG, `set statusBar failed, Cause: ${JSON.stringify(err)}`);
166      })
167    })
168  }
169
170  createWindow(context: common.ServiceExtensionContext, name: string, windowType: number, loadContent: string,
171               isShow: boolean, callback?: Function) {
172    let cfg: window.Configuration = {
173      name: name,
174      windowType: windowType,
175      ctx: context
176    };
177    try {
178      window.createWindow(cfg)
179        .then((win: window.Window) => {
180          win.setPreferredOrientation(window.Orientation.AUTO_ROTATION_RESTRICTED);
181          win.setUIContent(loadContent)
182            .then(() => {
183              win.setWindowSystemBarProperties({
184                navigationBarColor: StyleConstants.DEFAULT_SYSTEM_UI_COLOR,
185                statusBarColor: StyleConstants.DEFAULT_SYSTEM_UI_COLOR
186              }).then(() => {
187                win.setWindowBackgroundColor(StyleConstants.DEFAULT_SYSTEM_UI_COLOR);
188                Log.showDebug(TAG, `then begin ${name} window loadContent in then!`);
189                if (name !== this.RECENT_WINDOW_NAME) {
190                  win.setWindowLayoutFullScreen(true).then(() => {
191                    Log.showDebug(TAG, `${name} setLayoutFullScreen`);
192                  });
193                }
194                if (callback) {
195                  callback(win);
196                }
197                // there is a low probability that white flashes when no delay because of setBackgroundColor is asynchronous
198                setTimeout(() => {
199                  isShow && this.showWindow(name);
200                }, StyleConstants.WINDOW_SHOW_DELAY)
201              });
202            }, (err: BusinessError) => {
203              Log.showError(TAG, `createWindow, setUIContent error: ${JSON.stringify(err)}`);
204            });
205        })
206        .catch((err: BusinessError) => {
207          Log.showError(TAG, `createWindow, createWindow error: ${JSON.stringify(err)}`);
208        })
209    } catch (err) {
210      let _err = err as BusinessError;
211      Log.showError(TAG, `createWindow, error: ${JSON.stringify(_err)}`);
212    }
213  }
214
215  createWindowIfAbsent(context: common.ServiceExtensionContext, name: string, windowType: number, loadContent: string): void {
216    Log.showDebug(TAG, `create, name ${name}`);
217    try {
218      let win: window.Window = window.findWindow(name);
219      win.showWindow().then(() => {
220        Log.showDebug(TAG, `show launcher ${name}`);
221      });
222    } catch (err) {
223      let _err = err as BusinessError;
224      Log.showError(TAG, `${name} ability is not created, because ${_err.message}`);
225      this.createWindow(context, name, windowType, loadContent, true);
226    }
227  }
228
229  resetSizeWindow(name: string, rect: {
230    width: number,
231    height: number
232  }, callback?: Function): void {
233    Log.showDebug(TAG, `resetSizeWindow, name ${name} rect: ${JSON.stringify(rect)}`);
234    this.findWindow(name, (win) => {
235      Log.showDebug(TAG, `resetSizeWindow, findWindow callback name: ${name}`);
236      win.resetSize(rect.width, rect.height).then(() => {
237        Log.showDebug(TAG, `resetSizeWindow, resetSize then name: ${name}`);
238        if (callback) {
239          callback(win);
240        }
241      });
242    });
243  }
244
245  showWindow(name: string, callback?: Function): void {
246    Log.showDebug(TAG, `showWindow, name ${name}`);
247    this.findWindow(name, (win) => {
248      Log.showDebug(TAG, `showWindow, findWindow callback name: ${name}`);
249      win.show().then(() => {
250        Log.showDebug(TAG, `showWindow, show then name: ${name}`);
251        if (callback) {
252          callback(win);
253        }
254      });
255    });
256  }
257
258  hideWindow(name: string, callback?: Function): void {
259    Log.showDebug(TAG, `hideWindow, name ${name}`);
260    this.findWindow(name, (win) => {
261      Log.showDebug(TAG, `hideWindow, findWindow callback name: ${name}`);
262      win.hide().then(() => {
263        Log.showDebug(TAG, `hideWindow, hide then name: ${name}`);
264        if (callback) {
265          callback(win);
266        }
267      });
268    });
269  }
270
271  minimizeAllApps(): void {
272    try {
273      let dis: display.Display = display.getDefaultDisplaySync();
274      window.minimizeAll(dis.id).then(() => {
275        Log.showDebug(TAG, 'Launcher minimizeAll');
276      });
277    } catch (err) {
278      let errCode = (err as BusinessError).code;
279      let errMsg = (err as BusinessError).message;
280      Log.showError(TAG, `minimizeAllApps errCode: ${errCode}, errMsg: ${errMsg}`);
281    }
282    this.destroyWindow(this.FORM_MANAGER_WINDOW_NAME);
283    this.destroyWindow(this.FORM_SERVICE_WINDOW_NAME);
284  }
285
286  destroyWindow(name: string, callback?: Function): void {
287    Log.showDebug(TAG, `destroyWindow, name ${name}`);
288    this.findWindow(name, (win) => {
289      Log.showDebug(TAG, `hideWindow, findWindow callback name: ${name}`);
290      win.destroy().then(() => {
291        Log.showDebug(TAG, `destroyWindow, destroy then name: ${name}`);
292        if (callback) {
293          callback(win);
294        }
295      });
296    });
297  }
298
299  findWindow(name: string, callback?: Function): void {
300    Log.showDebug(TAG, `findWindow, name ${name}`);
301    try {
302      let win: window.Window = window.findWindow(name);
303      Log.showDebug(TAG, `findWindow, find then name: ${name}`);
304      if (callback) {
305        callback(win);
306      }
307    } catch (err) {
308      let _err = err as BusinessError;
309      Log.showError(TAG, `findWindow errCode: ${_err.code}, errMsg: ${_err.message}`);
310    }
311  }
312
313  createRecentWindow(mode?: number) {
314    Log.showDebug(TAG, 'createRecentWindow Begin, mode=' + mode);
315    let setWinMode = (mode && this.isSplitWindowMode(mode)) ? (win) => {
316      windowManager.recentMode = mode;
317      win.setWindowMode(mode).then();
318    } : (win) => {
319      windowManager.recentMode = AbilityConstant.WindowMode.WINDOW_MODE_FULLSCREEN;
320      win.setFullScreen(true).then(() => {
321        Log.showDebug(TAG, `${this.RECENT_WINDOW_NAME} setFullScreen`);
322      });
323    };
324    try {
325      let win: window.Window = window.findWindow(windowManager.RECENT_WINDOW_NAME);
326      setWinMode(win);
327      win.showWindow()
328        .then(() => {
329          Log.showDebug(TAG, 'show launcher recent ability');
330        });
331    } catch (err) {
332      Log.showDebug(TAG, `recent window is not created, because ${JSON.stringify(err)}`);
333      let callback = (win) => {
334        Log.showDebug(TAG, 'Post recent window created');
335        setWinMode(win);
336      }
337      this.createWindow(globalThis.desktopContext, windowManager.RECENT_WINDOW_NAME, windowManager.RECENT_RANK,
338        'pages/' + windowManager.RECENT_WINDOW_NAME, false, callback);
339    }
340  }
341
342  destroyRecentWindow() {
343    this.findWindow(windowManager.RECENT_WINDOW_NAME, win => {
344      win.off('windowEvent', (win) => {
345        win.destroy().then(() => {
346          Log.showDebug(TAG, 'destroyRecentWindow');
347        });
348      })
349    });
350  }
351
352  private static initSubscriber() {
353    if (WindowManager.subscriber != null) {
354      return;
355    }
356    const subscribeInfo: commonEventMgr.CommonEventSubscribeInfo = {
357      events: [commonEventManager.RECENT_FULL_SCREEN, commonEventManager.RECENT_SPLIT_SCREEN]
358    };
359    commonEventMgr.createSubscriber(subscribeInfo).then((commonEventSubscriber: commonEventMgr.CommonEventSubscriber) => {
360      Log.showDebug(TAG, 'init SPLIT_SCREEN subscriber success');
361      WindowManager.subscriber = commonEventSubscriber;
362    }, (err) => {
363      Log.showError(TAG, `Failed to createSubscriber ${err}`)
364    })
365  }
366
367  /**
368   * Register window event listener.
369   */
370  public registerWindowEvent() {
371    commonEventManager.registerCommonEvent(WindowManager.subscriber, WindowManager.eventCallback);
372  }
373
374  /**
375   * Unregister window event listener.
376   */
377  public unregisterWindowEvent() {
378    commonEventManager.unregisterCommonEvent(WindowManager.subscriber, WindowManager.eventCallback);
379  }
380
381  /**
382   * Window event handler.
383   */
384  private static async winEventCallback(error: BusinessError, data: commonEventMgr.CommonEventData) {
385    Log.showDebug(TAG,`Launcher WindowManager winEventCallback receive data: ${JSON.stringify(data)}.`);
386    if (data.code !== 0) {
387      Log.showError(TAG, `get winEventCallback error: ${JSON.stringify(error)}`);
388      return;
389    }
390
391    switch (data.event) {
392      case commonEventManager.RECENT_FULL_SCREEN:
393        // full screen recent window
394        windowManager.minimizeAllApps();
395        windowManager.createRecentWindow();
396        break;
397      case commonEventManager.RECENT_SPLIT_SCREEN:
398        // split window mode
399        const windowModeMap = {
400          'Primary': AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_PRIMARY,
401          'Secondary': AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_SECONDARY
402        };
403        if (data.parameters.windowMode !== 'Primary' && data.parameters.windowMode !== 'Secondary') {
404          break;
405        }
406        windowManager.createRecentWindow(windowModeMap[data.parameters.windowMode]);
407        globalThis.splitMissionId = data.parameters.missionId;
408        await WindowManager.subscriber.setCode(0);
409        await WindowManager.subscriber.finishCommonEvent();
410        break;
411      default:
412        break;
413    }
414  }
415
416  /**
417   * Screen rotation callback.
418   */
419  public async onPortrait(mediaQueryResult) {
420    if (mediaQueryResult.matches) {
421      Log.showInfo(TAG, 'screen change to landscape');
422      AppStorage.setOrCreate('isPortrait', false);
423    } else {
424      Log.showInfo(TAG, 'screen change to portrait');
425      AppStorage.setOrCreate('isPortrait', true);
426    }
427    try {
428      let dis: display.Display = display.getDefaultDisplaySync();
429      Log.showInfo(TAG, `change to display: ${JSON.stringify(dis)}`);
430      AppStorage.setOrCreate('screenWidth', px2vp(dis.width));
431      AppStorage.setOrCreate('screenHeight', px2vp(dis.height));
432      Log.showDebug(TAG, `screenWidth and screenHeight: ${AppStorage.get('screenWidth')},${AppStorage.get('screenHeight')}`);
433    } catch (err) {
434      Log.showError(TAG, `onPortrait error: ${JSON.stringify(err)}`);
435    }
436  }
437
438  createWindowWithName = ((windowName: string, windowRank: number): void => {
439    Log.showInfo(TAG, `createWindowWithName begin windowName: ${windowName}`);
440    if (windowName === windowManager.RECENT_WINDOW_NAME) {
441      windowManager.createRecentWindow();
442    } else {
443      windowManager.createWindowIfAbsent(globalThis.desktopContext, windowName, windowRank, 'pages/' + windowName);
444    }
445  })
446
447}
448
449export const windowManager = WindowManager.getInstance();