1/**
2 * Copyright (c) 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 */
15import Notification from '@ohos.notificationManager'
16import WantAgent from '@ohos.app.ability.wantAgent';
17import { HiLog, sharedPreferencesUtils } from '../../../../../../common';
18import notificationSubscribe from '@ohos.notificationSubscribe';
19import call from '@ohos.telephony.call';
20import { NotificationSubscriber } from 'notification/notificationSubscriber';
21
22const TAG = 'MissedCallNotifier';
23
24const BUNDLE_NAME: string = 'com.ohos.contacts';
25const ABILITY_NAME: string = 'com.ohos.contacts.MainAbility';
26const GROUP_NAME: string = 'MissedCall'
27const KEY_MISSED_BADGE_NUM = 'missed_badge_number'
28const KEY_ID = 'unread_call_notification_id'
29const KEY_DISPLAY_NAME = 'unread_call_notification_displayName'
30const KEY_COUNT = 'unread_call_notification_count'
31const KEY_CREATE_TIME = 'unread_call_notification_create_time'
32const KEY_RING_DURATION = 'unread_call_notification_ring_duration'
33const actionBtnMaps =
34  {
35    'notification.event.dialBack': $r('app.string.dial_back'),
36    'notification.event.message': $r('app.string.message'),
37  };
38
39export class MissedCallNotifyData {
40  id?: number;
41  displayName?: string;
42  readonly phoneNumber: string;
43  count?: number;
44  createTime?: number;
45  ringDuration?: number;
46}
47
48export class MissedCallNotifier {
49  private label: string;
50  private context: Context;
51  private static sInstance: MissedCallNotifier = undefined;
52  private missedBadgeNumber: number = -1;
53  private UnReadMissedCallData: MissedCallNotifyData;
54
55  /**
56   * getInstance for MissedCallNotifier
57   */
58  public static getInstance() {
59    if (!MissedCallNotifier.sInstance || MissedCallNotifier.sInstance == undefined) {
60      MissedCallNotifier.sInstance = new MissedCallNotifier();
61    }
62    return MissedCallNotifier.sInstance;
63  }
64
65  /**
66   * init
67   *
68   * @param ctx context needed init
69   */
70  public init(ctx: Context) {
71    this.context = ctx;
72    if (!this.label) {
73      this.label = this.getContext()?.resourceManager.getStringSync($r('app.string.missed_call'));
74    }
75    sharedPreferencesUtils.init(this.getContext());
76  }
77
78  /**
79   * update Missed Call Notification
80   *
81   * @param missedData missedCallData - missed call data for notification
82   */
83  public async updateMissedCallNotifications(missedData: Map<string, MissedCallNotifyData>) {
84    let notifyData = await this.getMissedCallNotifyDatas();
85    HiLog.i(TAG, `updateMissedCallNotifications notifyData:${notifyData.length} missedData: ${missedData.size}`)
86    let badgeNumber: number = 0;
87    if (notifyData.length > 0) {
88      for (let notify of notifyData) {
89        let key: string = notify.displayName;
90        if (missedData.has(key)) {
91          let missed = missedData.get(key)
92          missedData.delete(key)
93          if (missed.id != notify.id) {
94            notify.createTime = missed.createTime;
95            notify.ringDuration = missed.ringDuration;
96            notify.count += missed.count;
97            this.sendNotification(notify);
98          }
99        }
100        badgeNumber += notify.count;
101      }
102    }
103    for (let notify of missedData.values()) {
104      await this.sendNotification(notify);
105      badgeNumber += notify.count;
106    }
107    if (badgeNumber > 0) {
108      this.setMissedBadgeNumber(badgeNumber);
109    }
110  }
111
112  /**
113   * cancel Missed Call Notification
114   */
115  public cancelAllNotification() {
116    HiLog.i(TAG, 'cancelNotification,cancel all')
117    this.setMissedBadgeNumber(0);
118    Notification.cancelGroup(GROUP_NAME).catch(error => {
119      HiLog.e(TAG, `cancelNotification,err ${JSON.stringify(error)}}`)
120    });
121  }
122
123  /**
124   * get Missed Call BadgeNumber
125   */
126  public async getMissedBadgeNumber() {
127    if (this.missedBadgeNumber == -1) {
128      this.missedBadgeNumber = <number> await sharedPreferencesUtils.getFromPreferences(KEY_MISSED_BADGE_NUM, -1);
129    }
130    return this.missedBadgeNumber;
131  }
132
133  /**
134   * cancel Missed Call Notification By NotificationId
135   *
136   * @param id Notification Id
137   */
138  public async cancelNotificationById(id: number, count: number) {
139    HiLog.i(TAG, 'cancelNotificationById:' + id)
140    Notification.cancel(id, this.label).catch(error => {
141      HiLog.e(TAG, `cancelNotificationById,err ${JSON.stringify(error)}}`)
142    });
143    let badgeNumber = await this.getMissedBadgeNumber();
144    badgeNumber -= count;
145    if (badgeNumber >= 0) {
146      this.setMissedBadgeNumber(badgeNumber);
147    }
148  }
149
150  private constructor() {
151  }
152
153  private getContext() {
154    if (this.context && this.context != undefined) {
155      return this.context;
156    }
157    return globalThis.context;
158  }
159
160  private async getMissedCallNotifyDatas() {
161    HiLog.i(TAG, `getMissedCallNotifyDatas in`)
162    let result: Array<MissedCallNotifyData> = [];
163    const notifications: Array<Notification.NotificationRequest> =
164      await Notification.getAllActiveNotifications()
165    for (let notify of notifications) {
166      if (notify.groupName == GROUP_NAME && notify.extraInfo) {
167        result.push(<MissedCallNotifyData> notify.extraInfo);
168      }
169    }
170    HiLog.i(TAG, `getMissedCallNotifyDatas result: ${result.length}`)
171    return result;
172  }
173
174  private async sendNotification(missedCallData: MissedCallNotifyData) {
175    const {id, displayName, count, createTime, ringDuration} = missedCallData;
176    sharedPreferencesUtils.saveToPreferences(KEY_ID, id);
177    sharedPreferencesUtils.saveToPreferences(KEY_DISPLAY_NAME, displayName);
178    sharedPreferencesUtils.saveToPreferences(KEY_COUNT, count);
179    sharedPreferencesUtils.saveToPreferences(KEY_CREATE_TIME, createTime);
180    sharedPreferencesUtils.saveToPreferences(KEY_RING_DURATION, ringDuration)
181    HiLog.i(TAG, `sendNotification in id:${id}, count:${count}, createTime:${createTime}`)
182    let str_text = this.getContext()?.resourceManager.getStringSync($r('app.string.contacts_ring_times')) +
183    ringDuration + this.getContext()?.resourceManager.getStringSync($r('app.string.contacts_time_sec'));
184    if (ringDuration === 0) {
185      str_text = $r('app.string.missed_call')
186    }
187    const notificationRequest: Notification.NotificationRequest = {
188      content: {
189        contentType: Notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
190        normal: {
191          title: count > 1 ? `${displayName} (${count})` : displayName,
192          text: str_text,
193          additionalText: missedCallData.phoneNumber
194        },
195      },
196      id: id,
197      label: this.label,
198      groupName: GROUP_NAME,
199      slotType: Notification.SlotType.SOCIAL_COMMUNICATION,
200      deliveryTime: new Date().getTime(),
201      extraInfo: missedCallData
202    }
203
204    let wantAgentObj = await this.getWantAgent(missedCallData, 'notification.event.click');
205    notificationRequest.wantAgent = wantAgentObj;
206    notificationRequest.actionButtons = [];
207    for (const key of Object.keys(actionBtnMaps)) {
208      const wantAgent = await this.getWantAgent(missedCallData, key);
209      const title = this.getContext()?.resourceManager.getStringSync(actionBtnMaps[key]);
210      notificationRequest.actionButtons.push({
211        title: title,
212        wantAgent: wantAgent
213      });
214    }
215    notificationRequest.removalWantAgent = await this.createWantAgentForCommonEvent(missedCallData, 'notification.event.cancel');
216    Notification.publish(notificationRequest).then(() => {
217      HiLog.i(TAG, '===>publish promise success req.id : ' + notificationRequest.id);
218    }).catch((err) => {
219      HiLog.e(TAG, '===>publish promise failed because ' + JSON.stringify(err));
220    });
221    HiLog.i(TAG, 'sendNotification end')
222  }
223
224  /**
225   * send Unread Call  Notification
226   */
227  public async sendUnreadCallNotification(map: Map<string, string>) {
228    let id: number = <number> await sharedPreferencesUtils.getFromPreferences(KEY_ID, -1);
229    let displayName: string = <string> await  sharedPreferencesUtils.getFromPreferences(KEY_DISPLAY_NAME, '');
230    let count: number = <number> await  sharedPreferencesUtils.getFromPreferences(KEY_COUNT, -1);
231    let createTime: number = <number> await  sharedPreferencesUtils.getFromPreferences(KEY_CREATE_TIME, -1);
232    let ringDuration: number = <number> await  sharedPreferencesUtils.getFromPreferences(KEY_RING_DURATION, -1);
233    let missCallData = map.get('missedPhoneJson')
234    const parameters = JSON.parse(JSON.stringify(missCallData));
235    for (let i = 0; i < parameters.phoneNumberList.length; i++) {
236      const missedPhoneNumber = parameters.phoneNumberList[i]
237      const missedNum = parameters.countList[i]
238      if (i === (parameters.phoneNumberList.length -1)) {
239        this.UnReadMissedCallData = {
240          phoneNumber: missedPhoneNumber,
241          displayName: missedPhoneNumber,
242          id: i,
243          createTime: createTime,
244          count: count,
245          ringDuration: ringDuration
246        }
247      } else {
248        this.UnReadMissedCallData = {
249          phoneNumber: missedPhoneNumber,
250          displayName: missedPhoneNumber,
251          id: i,
252          createTime: createTime,
253          count: count,
254          ringDuration: 0
255        }
256      }
257      this.sendNotification(this.UnReadMissedCallData);
258    }
259  }
260
261  private setMissedBadgeNumber(newBadgeNum: number) {
262    HiLog.i(TAG, 'setMissedBadgeNumber :' + newBadgeNum);
263    this.missedBadgeNumber = newBadgeNum;
264    Notification.setBadgeNumber(newBadgeNum);
265    sharedPreferencesUtils.saveToPreferences(KEY_MISSED_BADGE_NUM, newBadgeNum);
266  }
267
268  /**
269   * create wantAgent for common event
270   *
271   * @param mAction
272   * @return return the created WantAgent object.
273   */
274  private async createWantAgentForCommonEvent(missedCallData, action?: string) {
275    return await WantAgent.getWantAgent({
276      wants: [{ action: 'contact.event.CANCEL_MISSED', parameters: {
277        action: action,
278        missedCallData: missedCallData
279      }, }],
280      operationType: WantAgent.OperationType.SEND_COMMON_EVENT,
281      requestCode: 0
282    })
283  }
284
285  private getWantAgent(missedCallData: MissedCallNotifyData, action: string) {
286    let data: any = {}
287    data.action = action,
288    data.missedCallData = missedCallData
289    if (action == 'notification.event.dialBack') {
290      HiLog.i(TAG, 'getWantAgent add page_flag_edit_before_calling')
291      return this.createWantAgentForCommonEvent(missedCallData, action);
292    }
293    return WantAgent.getWantAgent({
294      wants: [{
295                deviceId: '',
296                bundleName: BUNDLE_NAME,
297                abilityName: ABILITY_NAME,
298                uri: '',
299                type: 'phone',
300                parameters: data,
301                entities: []
302              }],
303      requestCode: 0,
304      operationType: WantAgent.OperationType.START_ABILITY,
305      wantAgentFlags: [WantAgent.WantAgentFlags.ONE_TIME_FLAG]
306    });
307  }
308}