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