1/*
2 * Copyright (c) 2024 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 */
15import UIExtensionAbility from '@ohos.app.ability.UIExtensionAbility';
16import window from '@ohos.window';
17import { Log, PermissionDialogException, setAvoidArea } from '../common/utils/utils';
18import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession';
19import Want from '@ohos.app.ability.Want';
20import uiExtensionHost from '@ohos.uiExtensionHost';
21import display from '@ohos.display';
22import { BusinessError } from '@ohos.base';
23import Constants from '../common/utils/constant';
24import AbilityConstant from '@ohos.app.ability.AbilityConstant';
25
26let terminating: boolean = false;
27let state: boolean = false;
28
29export default class GlobalSwitchSheetAbility extends UIExtensionAbility {
30  private subWindowForHost: window.Window | null = null;
31  private shouldChangeState: boolean = true;
32
33  onCreate(launchParam: AbilityConstant.LaunchParam): void {
34    Log.info('GlobalSwitchSheetAbility on create');
35  }
36
37  onSessionCreate(want: Want, session: UIExtensionContentSession): void {
38    Log.info('current state:' + state);
39    if (state == true) {
40      this.shouldChangeState = false;
41      Log.error('window state is already set');
42      PermissionDialogException(Constants.ERR_MODAL_ALREADY_EXIST, session);
43      this.context.terminateSelf();
44      return;
45    } else {
46      Log.info('set window state to true');
47      state = true;
48    }
49
50    let param: Record<string, Object> = {
51      'session': session, 'want': want
52    };
53
54    let storage: LocalStorage = new LocalStorage(param);
55    this.createSubWindow(session, storage).then(() => {
56      // if met error, do not load window,
57      // in case of nested error codes passed to applications
58      if (terminating) {
59        this.context.terminateSelf();
60        return;
61      }
62      this.loadContentAndSetColor(storage).then((result: boolean) => {
63        if (!result) {
64          return;
65        }
66        this.showWindow();
67      })
68    })
69  }
70
71  onSessionDestroy(session: UIExtensionContentSession): void {
72    Log.info('OnSessionDestroy start');
73    try {
74      Log.info('try to reshow non-secure windows.');
75      this.subWindowForHost?.hideNonSystemFloatingWindows(false);
76    } catch (error) {
77      Log.error('failed to reshowing the non-secure windows. Cause: ' + JSON.stringify(error));
78    }
79    try {
80      this.subWindowForHost?.off('windowEvent');
81    } catch (exception) {
82      Log.error('fail to call off window event: ' + JSON.stringify(exception));
83    }
84    this.subWindowForHost?.destroyWindow((err, data) => {
85      if (err && err.code !== 0) {
86        Log.info('destroy window:' + err.message + ' ' + JSON.stringify(data));
87      }
88    });
89    Log.info('destroy window end');
90  }
91
92  onForeground(): void {
93    Log.info('onForegorund');
94    this.subWindowForHost?.showWindow();
95  }
96
97  onBackground(): void {
98    Log.info('extension onBackground');
99    this.subWindowForHost?.hide();
100  }
101
102  onDestroy(): void | Promise<void> {
103    Log.info('onDestroy');
104    if (this.shouldChangeState == true) {
105      state = false;
106      Log.info('set window state to false');
107    }
108    this.subWindowForHost = null;
109  }
110
111  async loadContentAndSetColor(storage: LocalStorage): Promise<boolean> {
112    let promise: Promise<void> | undefined =
113      this.subWindowForHost?.loadContent('PermissionSheet/GlobalSwitchSheetDialog', storage);
114    if (promise) {
115      await promise.then(() => {
116        Log.info('Succeeded in loading the content');
117        try {
118          this.subWindowForHost?.setWindowBackgroundColor('#00000000');
119        } catch (error) {
120          Log.error(`setWindowBackgroundColor failed, error code ${JSON.stringify(error)}`);
121        }
122      }).catch((err: BusinessError) => {
123        Log.error('Failed to load the content. Cause:' + JSON.stringify(err));
124        return false;
125      })
126    }
127    return true;
128  }
129
130  async createSubWindow(session: UIExtensionContentSession, storage: LocalStorage): Promise<void> {
131    let extensionWinProxy: uiExtensionHost.UIExtensionHostWindowProxy = session?.getUIExtensionHostWindowProxy();
132    setAvoidArea(extensionWinProxy);
133    let subWindowOpts: window.SubWindowOptions = {
134      title: '', decorEnabled: false, isModal: true,
135    };
136    if (!extensionWinProxy) {
137      Log.error('extensionWinProxy is nullptr!');
138      return;
139    }
140    // try to create subwindow first
141    await extensionWinProxy.createSubWindowWithOptions('permissionSubWindowForHost' + Date(), subWindowOpts)
142      .then((subWindow: window.Window)=>{
143        this.subWindowForHost = subWindow;
144      }).catch((err: BusinessError) => {
145        Log.error('create sub window error:' + err.message + ' ' + err.code);
146      });
147    // if create subwindow fails, try create floating window
148    if (!this.subWindowForHost) {
149      Log.error('subwindow is null, failed to create subwindow, tray create float window');
150      await this.createFloatingWindow(session);
151      if (!this.subWindowForHost) {
152        terminating = true;
153        PermissionDialogException(Constants.ERR_MODAL_ALREADY_EXIST, session);
154        this.context.terminateSelf();
155        Log.error('floating is null, failed to create float window, terminate self');
156        return;
157      }
158    }
159    let dis = display.getDefaultDisplaySync();
160    await this.subWindowForHost?.resize(dis.width, dis.height).catch((err: BusinessError) => {
161      Log.error('resize window error: ' + err.code + ' ' + err.message);
162    });
163    this.subWindowForHost?.on('windowEvent', (eventType: window.WindowEventType) => {
164      Log.info('window event type is: ' + eventType);
165      storage.setOrCreate('permissionWindow', eventType);
166    })
167    Log.info('create and resize window success');
168    return;
169  }
170
171  async createFloatingWindow(session: UIExtensionContentSession): Promise<void> {
172    let config: window.Configuration = {
173      name: 'PermissionFloatWindow' + Date(),
174      windowType: window.WindowType.TYPE_FLOAT,
175      ctx: this.context
176    }
177    let promise: Promise<window.Window> | undefined = window.createWindow(config);
178    await promise.then((floatWindow: window.Window) => {
179      this.subWindowForHost = floatWindow;
180    }).catch((err: BusinessError) => {
181      Log.error('create floating window error:' + err.message + ' ' + err.code);
182      // if modal already exist , return
183      if (err.code === Constants.CREATE_WINDOW_REPEATED) {
184        Log.error('try to create subwindow repeatedly');
185        terminating = true;
186        PermissionDialogException(Constants.ERR_MODAL_ALREADY_EXIST, session);
187        this.context.terminateSelf();
188      }
189    });
190    if (!this.subWindowForHost) {
191      Log.error('create floating fail');
192      return;
193    }
194    Log.info('create floating window success');
195  }
196
197  showWindow(): void {
198    this.subWindowForHost?.showWindow((err) => {
199      if (err && err.code !== 0) {
200        Log.error(`failed to showWindow for subWindow, ${JSON.stringify(err)}`);
201        return;
202      }
203      this.subWindowForHost?.hideNonSystemFloatingWindows(true);
204      Log.info('show window and set non floating success');
205    })
206  }
207}