• 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
94  constructor(id: number, want: Want, stageModel: boolean) {
95    this.id = id;
96    this.want = want;
97    this.stageModel = stageModel;
98    this.window = undefined;
99    this.extensionWindow = undefined;
100  }
101
102
103  async createUiExtensionWindow(session: UIExtensionContentSession, stageModel: boolean): Promise<void> {
104    try {
105      let extensionWindow = session.getUIExtensionHostWindowProxy();
106      this.extensionWindow = extensionWindow;
107      let shouldHide = true;
108
109      this.storage = new LocalStorage({
110        'dialog': this,
111        'session': session
112      });
113
114      let path = EnableNotificationDialog.DIALOG_PATH;
115      let hasConfig = true;
116      try {
117        let filePaths = await configPolicy.getCfgFiles(Constants.CCM_CONFIG_PATH);
118        if (filePaths.length === 0) {
119          console.info(TAG, 'not get any configFile');
120          hasConfig = false;
121        }
122        for (let i = 0; i < filePaths.length; i++) {
123          let res = fs.accessSync(filePaths[i]);
124          if (res) {
125            let fileContent = fs.readTextSync(filePaths[i]);
126            let config: NotificationConfig = JSON.parse(fileContent);
127            if (config.deviceInfo !== undefined) {
128              let deviceInfo: DeviceInfo = config.deviceInfo;
129              if (deviceInfo.isWatch !== undefined) {
130                path = EnableNotificationDialog.WATCH_DIALOG_PATH;
131                console.info(TAG, 'watch request');
132              }
133              if (deviceInfo.isPc !== undefined) {
134                path = EnableNotificationDialog.PC_DIALOG_PATH;
135                console.info(TAG, 'pc request');
136              }
137            }
138          }
139        }
140      } catch (err) {
141        console.error(TAG, 'Failed get ccm files');
142      }
143
144      if (stageModel && hasConfig) {
145        let subWindowOpts : window.SubWindowOptions = {
146          'title': '',
147          decorEnabled: false,
148          isModal: true,
149          isTopmost: true
150        };
151        let subWindow = await extensionWindow.createSubWindowWithOptions('subWindowForHost' + Date(), subWindowOpts);
152        this.subWindow = subWindow;
153        await this.sleep(200);
154        let windowRect = extensionWindow.properties?.uiExtensionHostWindowProxyRect;
155        console.info(TAG, `size : ${windowRect?.left} ${windowRect?.top} ${windowRect?.width}  ${windowRect?.height}`);
156        await subWindow.moveWindowTo(windowRect?.left, windowRect?.top);
157        await subWindow.resize(windowRect?.width, windowRect?.height);
158        await subWindow.loadContent(path, this.storage);
159        try {
160          await subWindow.hideNonSystemFloatingWindows(true);
161        } catch (err) {
162          console.error(TAG, 'subWindow hideNonSystemFloatingWindows failed!');
163        }
164        await subWindow.setWindowBackgroundColor(EnableNotificationDialog.TRANSPARANT_COLOR);
165        await subWindow.showWindow();
166      } else {
167        await session.loadContent(path, this.storage);
168        try {
169          await extensionWindow.hideNonSecureWindows(shouldHide);
170        } catch (err) {
171          console.error(TAG, 'window hideNonSecureWindows failed!');
172        }
173        await session.setWindowBackgroundColor(EnableNotificationDialog.TRANSPARANT_COLOR);
174      }
175    } catch (err) {
176      console.error(TAG, 'window create failed!');
177      throw new Error('Failed to create window');
178    }
179  }
180
181  async sleep(ms: number): Promise<void> {
182    return new Promise(resolve => setTimeout(resolve, ms));
183  }
184
185  async publishButtonClickedEvent(enabled: boolean): Promise<void> {
186    CommonEventManager.publish(
187      COMMON_EVENT_NAME,
188      {
189        code: enabled ? DialogStatus.ALLOW_CLICKED : DialogStatus.DENY_CLICKED,
190        data: this.want.parameters.bundleName.toString(),
191        parameters: {
192          bundleName: this.want.parameters.bundleName.toString(),
193          bundleUid: this.want.parameters.bundleUid.toString()
194        }
195      } as CommonEventManager.CommonEventPublishData,
196      () => { console.info(TAG, 'publish CLICKED succeeded'); }
197    );
198  }
199
200  async destroyException(): Promise<void> {
201    await handleDialogQuitException(this.want);
202  }
203
204  async destroy(): Promise<void> {
205    if (this.window !== undefined) {
206      emitter.emit(enableNotificationDialogDestroyedEvent, {
207        data: {
208          'id': this.id
209        }
210      });
211      await this.destroyWindow();
212    }
213  }
214
215  async destroyWindow(): Promise<void> {
216    await this.window.destroyWindow();
217    this.window = undefined;
218  }
219};
220
221
222class NotificationDialogServiceExtensionAbility extends UIExtensionAbility {
223
224  onCreate() {
225    console.log(TAG, `UIExtAbility onCreate`);
226    AppStorage.setOrCreate('context', this.context);
227    AppStorage.setOrCreate('isUpdate', UPDATE_INIT);
228    AppStorage.setOrCreate('clicked', false);
229    this.subscribe();
230
231  }
232
233  async onSessionCreate(want: Want, session: UIExtensionContentSession) {
234    try {
235      let stageModel = false;
236      let bundleName = want.parameters['ohos.aafwk.param.callerBundleName'];
237      let bundleUid = want.parameters['ohos.aafwk.param.callerUid'];
238      if (bundleName !== EnableNotificationDialog.SCENEBOARD_BUNDLE &&
239        bundleName !== EnableNotificationDialog.SYSTEMUI_BUNDLE) {
240        want.parameters.bundleName = bundleName;
241        want.parameters.bundleUid = bundleUid;
242        stageModel = true;
243      } else {
244        stageModel = false;
245      }
246      console.log(TAG, `UIExtAbility onSessionCreate bundleName ${want.parameters.bundleName}` +
247        `uid ${want.parameters.bundleUid}`);
248      let dialog = new EnableNotificationDialog(1, want, stageModel);
249      await dialog.createUiExtensionWindow(session, stageModel);
250      AppStorage.setOrCreate('dialog', dialog);
251    } catch (err) {
252      console.error(TAG, `Failed to handle onSessionCreate`);
253      await handleDialogQuitException(want);
254      this.context.terminateSelf();
255    }
256  }
257
258  onForeground() {
259    console.log(TAG, `UIExtAbility onForeground`);
260    let dialog = AppStorage.get<EnableNotificationDialog>('dialog');
261
262    if (dialog?.subWindow !== undefined) {
263      try {
264        dialog?.subWindow?.hideNonSystemFloatingWindows(true);
265      } catch (err) {
266        console.error(TAG, 'onForeground hideNonSystemFloatingWindows failed!');
267      }
268    } else {
269      try {
270        dialog?.extensionWindow?.hideNonSecureWindows(true);
271      } catch (err) {
272        console.error(TAG, 'onForeground hideNonSecureWindows failed!');
273      }
274    }
275  }
276
277  onBackground() {
278    console.log(TAG, `UIExtAbility onBackground`);
279    let dialog = AppStorage.get<EnableNotificationDialog>('dialog');
280
281    if (dialog?.subWindow !== undefined) {
282      try {
283        dialog?.subWindow?.hideNonSystemFloatingWindows(false);
284      } catch (err) {
285        console.error(TAG, 'onBackground hideNonSystemFloatingWindows failed!');
286      }
287    } else {
288      try {
289        dialog?.extensionWindow?.hideNonSecureWindows(false);
290      } catch (err) {
291        console.error(TAG, 'onBackground hideNonSecureWindows failed!');
292      }
293    }
294  }
295
296  async onSessionDestroy(session: UIExtensionContentSession): Promise<void> {
297    console.log(TAG, `UIExtAbility onSessionDestroy`);
298    if (AppStorage.get('clicked') === false) {
299      console.log(TAG, `UIExtAbility onSessionDestroy unclick destory`);
300      let dialog = AppStorage.get<EnableNotificationDialog>('dialog');
301      await dialog?.destroyException();
302    }
303  }
304
305  async onDestroy(): Promise<void> {
306    console.info(TAG, 'UIExtAbility onDestroy.');
307    await this.unsubscribe();
308    await this.sleep(500);
309    this.context.terminateSelf();
310  }
311
312  async sleep(ms: number): Promise<void> {
313      return new Promise(resolve => setTimeout(resolve, ms));
314  }
315
316  async subscribe(): Promise<void> {
317    await CommonEventManager.createSubscriber(
318      { events: ['usual.event.BUNDLE_RESOURCES_CHANGED'] })
319      .then((subscriber:CommonEventManager.CommonEventSubscriber) => {
320        eventSubscriber = subscriber;
321      })
322      .catch((err) => {
323        console.log(TAG, `subscriber createSubscriber error code is ${err.code}, message is ${err.message}`);
324      });
325
326    if (eventSubscriber === null) {
327      console.log(TAG, 'need create subscriber');
328      return;
329    }
330    CommonEventManager.subscribe(eventSubscriber, (err, data) => {
331      if (err?.code) {
332        console.error(TAG, `subscribe callBack err= ${JSON.stringify(err)}`);
333      } else {
334        console.log(TAG, `subscribe callBack data= ${JSON.stringify(data)}`);
335        if (data.parameters?.bundleResourceChangeType !== 1) {
336          return;
337        }
338        console.log(TAG, `BUNDLE_RESOURCES_CHANGED-language change`);
339        let isUpdate:number = AppStorage.get('isUpdate');
340        if (isUpdate === undefined || isUpdate > UPDATE_BOUNDARY) {
341          AppStorage.setOrCreate('isUpdate', UPDATE_NUM);
342        } else {
343          AppStorage.setOrCreate('isUpdate', ++isUpdate);
344        }
345      }
346    });
347  }
348
349  async unsubscribe(): Promise<void> {
350    try {
351      if (eventSubscriber != null) {
352        CommonEventManager.unsubscribe(eventSubscriber, (err) => {});
353      }
354    } catch (err) {
355      console.info('ubsubscribe fail');
356    }
357  }
358}
359
360
361export default NotificationDialogServiceExtensionAbility;
362