• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 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 display from '@ohos.display';
17import emitter from '@ohos.events.emitter';
18import extension from '@ohos.app.ability.ServiceExtensionAbility';
19import window from '@ohos.window';
20import CommonEventManager from '@ohos.commonEventManager';
21import type Want from '@ohos.app.ability.Want';
22import UIExtensionAbility from '@ohos.app.ability.UIExtensionAbility';
23import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession';
24import uiExtensionHost from '@ohos.uiExtensionHost';
25import StartOptions from '@ohos.app.ability.StartOptions';
26import configPolicy from '@ohos.configPolicy';
27import fs from '@ohos.file.fs';
28import Constants from '../common/constant';
29
30
31
32const TAG = 'NotificationDialog_Service ';
33
34const UPDATE_INIT = 1;
35const UPDATE_NUM = 1;
36const UPDATE_BOUNDARY = 100;
37
38let eventSubscriber:CommonEventManager.CommonEventSubscriber;
39
40const enableNotificationDialogDestroyedEvent = {
41  eventId: 1,
42  priority: emitter.EventPriority.LOW
43};
44
45const COMMON_EVENT_NAME = 'OnNotificationServiceDialogClicked';
46enum DialogStatus {
47  ALLOW_CLICKED,
48  DENY_CLICKED,
49  DIALOG_CRASHED,
50  DIALOG_SERVICE_DESTROYED
51};
52
53async function handleDialogQuitException(want: Want): Promise<void> {
54  CommonEventManager.publish(
55    COMMON_EVENT_NAME,
56    {
57      code: DialogStatus.DIALOG_CRASHED,
58      data: want.parameters.bundleName.toString(),
59      parameters: {
60        bundleName: want.parameters.bundleName.toString(),
61        bundleUid: want.parameters.bundleUid.toString()
62      }
63    } as CommonEventManager.CommonEventPublishData,
64    () => { console.info(TAG, 'publish DIALOG_CRASHED succeeded'); }
65  );
66}
67
68interface NotificationConfig {
69  deviceInfo: DeviceInfo;
70}
71
72interface DeviceInfo {
73  isWatch: boolean;
74  isPc: boolean;
75}
76
77export class EnableNotificationDialog {
78  static ENABLE_NOTIFICATION_DIALOG_NAME = 'EnableNotificationDialog';
79  static DIALOG_PATH = 'pages/notificationDialog';
80  static WATCH_DIALOG_PATH = 'pages/watchNotificationDialog';
81  static PC_DIALOG_PATH = 'pages/pcNotificationDialog';
82  static TRANSPARANT_COLOR = '#00000000';
83  static SCENEBOARD_BUNDLE = 'com.ohos.sceneboard';
84  static SYSTEMUI_BUNDLE = 'com.ohos.systemui';
85
86  id: number;
87  want: Want;
88  window: window.Window;
89  extensionWindow:uiExtensionHost.UIExtensionHostWindowProxy;
90  storage: LocalStorage;
91  stageModel: boolean;
92  subWindow: window.Window;
93  initSubWindowSize: boolean;
94
95  constructor(id: number, want: Want, stageModel: boolean) {
96    this.id = id;
97    this.want = want;
98    this.stageModel = stageModel;
99    this.window = undefined;
100    this.extensionWindow = undefined;
101    this.initSubWindowSize = false;
102  }
103
104
105  async createUiExtensionWindow(session: UIExtensionContentSession, stageModel: boolean): Promise<void> {
106    try {
107      let extensionWindow = session.getUIExtensionHostWindowProxy();
108      this.extensionWindow = extensionWindow;
109      let shouldHide = true;
110
111      this.storage = new LocalStorage({
112        'dialog': this,
113        'session': session
114      });
115
116      let path = EnableNotificationDialog.DIALOG_PATH;
117      let hasConfig = true;
118      let isPcDevice = false;
119      try {
120        let filePaths = await configPolicy.getCfgFiles(Constants.CCM_CONFIG_PATH);
121        if (filePaths.length === 0) {
122          console.info(TAG, 'not get any configFile');
123          hasConfig = false;
124        }
125        for (let i = 0; i < filePaths.length; i++) {
126          let res = fs.accessSync(filePaths[i]);
127          if (res) {
128            let fileContent = fs.readTextSync(filePaths[i]);
129            let config: NotificationConfig = JSON.parse(fileContent);
130            if (config.deviceInfo !== undefined) {
131              let deviceInfo: DeviceInfo = config.deviceInfo;
132              if (deviceInfo.isWatch !== undefined) {
133                path = EnableNotificationDialog.WATCH_DIALOG_PATH;
134                console.info(TAG, 'watch request');
135              }
136              if (deviceInfo.isPc !== undefined) {
137                path = EnableNotificationDialog.PC_DIALOG_PATH;
138                isPcDevice = true;
139                console.info(TAG, 'pc request');
140              }
141            }
142          }
143        }
144      } catch (err) {
145        console.error(TAG, 'Failed get ccm files');
146      }
147
148      if (stageModel && hasConfig) {
149        let subWindowOpts : window.SubWindowOptions = {
150          'title': '',
151          decorEnabled: false,
152          isModal: true,
153          isTopmost: true
154        };
155        let subWindow = await extensionWindow.createSubWindowWithOptions('subWindowForHost' + Date(), subWindowOpts);
156        this.subWindow = subWindow;
157
158        if(isPcDevice) {
159          let hasDisalogRectInfo = false;
160          let waiteTimes = 0;
161          extensionWindow.on('windowSizeChange', (data):void => {
162            console.info(TAG, `windowSizeChange ts event ${data?.width}, ${data?.height}`);
163            hasDisalogRectInfo = true;
164          });
165          while(!hasDisalogRectInfo && waiteTimes < 10){
166            waiteTimes ++;
167            await this.sleep(200);
168          }
169          if(hasDisalogRectInfo) {
170            let windowRect = extensionWindow.properties?.uiExtensionHostWindowProxyRect;
171            console.info(TAG, `size : ${windowRect?.left} ${windowRect?.top} ${windowRect?.width}  ${windowRect?.height}`);
172            await subWindow.moveWindowToGlobal(windowRect?.left, windowRect?.top);
173            await subWindow.resize(windowRect?.width, windowRect?.height);
174            hasDisalogRectInfo = false;
175          } else {
176            console.info(TAG,'waite send windwow info fail');
177            throw new Error('Failed to create window');
178          }
179        } else {
180          let windowRect = extensionWindow.properties?.uiExtensionHostWindowProxyRect;
181          console.info(TAG, `size : ${windowRect?.left} ${windowRect?.top} ${windowRect?.width}  ${windowRect?.height}`);
182          if (windowRect.width > 0 && windowRect.height > 0) {
183            console.log(TAG, `valid rect data`);
184            await subWindow.moveWindowToGlobal(windowRect?.left, windowRect?.top);
185            await subWindow.resize(windowRect?.width, windowRect?.height);
186            this.initSubWindowSize = true;
187          }
188        }
189        await subWindow.loadContent(path, this.storage);
190        try {
191          await subWindow.hideNonSystemFloatingWindows(true);
192        } catch (err) {
193          console.error(TAG, 'subWindow hideNonSystemFloatingWindows failed!');
194        }
195        await subWindow.setWindowBackgroundColor(EnableNotificationDialog.TRANSPARANT_COLOR);
196        await subWindow.showWindow();
197      } else {
198        await session.loadContent(path, this.storage);
199        try {
200          await extensionWindow.hideNonSecureWindows(shouldHide);
201        } catch (err) {
202          console.error(TAG, 'window hideNonSecureWindows failed!');
203        }
204        await session.setWindowBackgroundColor(EnableNotificationDialog.TRANSPARANT_COLOR);
205      }
206    } catch (err) {
207      console.error(TAG, 'window create failed!');
208      throw new Error('Failed to create window');
209    }
210  }
211
212  async sleep(ms: number): Promise<void> {
213    return new Promise(resolve => setTimeout(resolve, ms));
214  }
215
216  async publishButtonClickedEvent(enabled: boolean): Promise<void> {
217    CommonEventManager.publish(
218      COMMON_EVENT_NAME,
219      {
220        code: enabled ? DialogStatus.ALLOW_CLICKED : DialogStatus.DENY_CLICKED,
221        data: this.want.parameters.bundleName.toString(),
222        parameters: {
223          bundleName: this.want.parameters.bundleName.toString(),
224          bundleUid: this.want.parameters.bundleUid.toString()
225        }
226      } as CommonEventManager.CommonEventPublishData,
227      () => { console.info(TAG, 'publish CLICKED succeeded'); }
228    );
229  }
230
231  async destroyException(): Promise<void> {
232    await handleDialogQuitException(this.want);
233  }
234
235  async destroy(): Promise<void> {
236    if (this.window !== undefined) {
237      emitter.emit(enableNotificationDialogDestroyedEvent, {
238        data: {
239          'id': this.id
240        }
241      });
242      await this.destroyWindow();
243    }
244  }
245
246  async destroyWindow(): Promise<void> {
247    await this.window.destroyWindow();
248    this.window = undefined;
249  }
250};
251
252
253class NotificationDialogServiceExtensionAbility extends UIExtensionAbility {
254
255  onCreate() {
256    console.log(TAG, `UIExtAbility onCreate`);
257    AppStorage.setOrCreate('context', this.context);
258    AppStorage.setOrCreate('isUpdate', UPDATE_INIT);
259    AppStorage.setOrCreate('clicked', false);
260    this.subscribe();
261
262  }
263
264  async onSessionCreate(want: Want, session: UIExtensionContentSession) {
265    try {
266      let stageModel = false;
267      let bundleName = want.parameters['ohos.aafwk.param.callerBundleName'];
268      let bundleUid = want.parameters['ohos.aafwk.param.callerUid'];
269      if (bundleName !== EnableNotificationDialog.SCENEBOARD_BUNDLE &&
270        bundleName !== EnableNotificationDialog.SYSTEMUI_BUNDLE) {
271        want.parameters.bundleName = bundleName;
272        want.parameters.bundleUid = bundleUid;
273        stageModel = true;
274      } else {
275        stageModel = false;
276      }
277      console.log(TAG, `UIExtAbility onSessionCreate bundleName ${want.parameters.bundleName}` +
278        `uid ${want.parameters.bundleUid}`);
279      let dialog = new EnableNotificationDialog(1, want, stageModel);
280      await dialog.createUiExtensionWindow(session, stageModel);
281      AppStorage.setOrCreate('dialog', dialog);
282    } catch (err) {
283      console.error(TAG, `Failed to handle onSessionCreate`);
284      await handleDialogQuitException(want);
285      this.context.terminateSelf();
286    }
287  }
288
289  onForeground() {
290    console.log(TAG, `UIExtAbility onForeground`);
291    let dialog = AppStorage.get<EnableNotificationDialog>('dialog');
292
293    if (dialog?.subWindow !== undefined) {
294      try {
295        dialog?.subWindow?.hideNonSystemFloatingWindows(true);
296      } catch (err) {
297        console.error(TAG, 'onForeground hideNonSystemFloatingWindows failed!');
298      }
299    } else {
300      try {
301        dialog?.extensionWindow?.hideNonSecureWindows(true);
302      } catch (err) {
303        console.error(TAG, 'onForeground hideNonSecureWindows failed!');
304      }
305    }
306  }
307
308  onBackground() {
309    console.log(TAG, `UIExtAbility onBackground`);
310    let dialog = AppStorage.get<EnableNotificationDialog>('dialog');
311
312    if (dialog?.subWindow !== undefined) {
313      try {
314        dialog?.subWindow?.hideNonSystemFloatingWindows(false);
315      } catch (err) {
316        console.error(TAG, 'onBackground hideNonSystemFloatingWindows failed!');
317      }
318    } else {
319      try {
320        dialog?.extensionWindow?.hideNonSecureWindows(false);
321      } catch (err) {
322        console.error(TAG, 'onBackground hideNonSecureWindows failed!');
323      }
324    }
325  }
326
327  async onSessionDestroy(session: UIExtensionContentSession): Promise<void> {
328    console.log(TAG, `UIExtAbility onSessionDestroy`);
329    if (AppStorage.get('clicked') === false) {
330      console.log(TAG, `UIExtAbility onSessionDestroy unclick destory`);
331      let dialog = AppStorage.get<EnableNotificationDialog>('dialog');
332      await dialog?.destroyException();
333    }
334  }
335
336  async onDestroy(): Promise<void> {
337    console.info(TAG, 'UIExtAbility onDestroy.');
338    await this.unsubscribe();
339    await this.sleep(500);
340    this.context.terminateSelf();
341  }
342
343  async sleep(ms: number): Promise<void> {
344      return new Promise(resolve => setTimeout(resolve, ms));
345  }
346
347  async subscribe(): Promise<void> {
348    await CommonEventManager.createSubscriber(
349      { events: ['usual.event.BUNDLE_RESOURCES_CHANGED'] })
350      .then((subscriber:CommonEventManager.CommonEventSubscriber) => {
351        eventSubscriber = subscriber;
352      })
353      .catch((err) => {
354        console.log(TAG, `subscriber createSubscriber error code is ${err.code}, message is ${err.message}`);
355      });
356
357    if (eventSubscriber === null) {
358      console.log(TAG, 'need create subscriber');
359      return;
360    }
361    CommonEventManager.subscribe(eventSubscriber, (err, data) => {
362      if (err?.code) {
363        console.error(TAG, `subscribe callBack err= ${JSON.stringify(err)}`);
364      } else {
365        console.log(TAG, `subscribe callBack data= ${JSON.stringify(data)}`);
366        if (data.parameters?.bundleResourceChangeType !== 1) {
367          return;
368        }
369        console.log(TAG, `BUNDLE_RESOURCES_CHANGED-language change`);
370        let isUpdate:number = AppStorage.get('isUpdate');
371        if (isUpdate === undefined || isUpdate > UPDATE_BOUNDARY) {
372          AppStorage.setOrCreate('isUpdate', UPDATE_NUM);
373        } else {
374          AppStorage.setOrCreate('isUpdate', ++isUpdate);
375        }
376      }
377    });
378  }
379
380  async unsubscribe(): Promise<void> {
381    try {
382      if (eventSubscriber != null) {
383        CommonEventManager.unsubscribe(eventSubscriber, (err) => {});
384      }
385    } catch (err) {
386      console.info('ubsubscribe fail');
387    }
388  }
389}
390
391
392export default NotificationDialogServiceExtensionAbility;
393