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 */
15
16import { HiLog, sharedPreferencesUtils } from 'common';
17import { MissedCallNotifier, MissedCallNotifyData } from './MissedCallNotifier'
18import { CallLogRepository } from '../repo/CallLogRepository';
19import { ContactRepository } from '../../../../../../feature/contact/src/main/ets/repo/ContactRepository';
20import { StringUtil } from '../../../../../../common/src/main/ets/util/StringUtil';
21import { CallLog } from '../entity/CallLog'
22
23const TAG = 'MissedCallService';
24const LAST_MISSED_ID: string = 'Last_Missed_id'
25const PREFERENCE_NAME: string = 'CONTACT_PREFERENCE';
26
27export class MissedCallService {
28  private static sInstance: MissedCallService = undefined;
29  private context: Context = undefined;
30  private lastMissedId = -1;
31  private onCallLogChanged = () => {
32    if (this.lastMissedId != -1) {
33      HiLog.i(TAG, 'onCallLogChanged lastMissedId:' + this.lastMissedId);
34      this.sendMissedCallNotify(this.lastMissedId);
35    }
36    this.unRegisterDataChangeObserver();
37  }
38
39  /**
40   * getInstance for MissedCallService
41   */
42  public static getInstance() {
43    if (!MissedCallService.sInstance || MissedCallService.sInstance == undefined) {
44      MissedCallService.sInstance = new MissedCallService();
45    }
46    return MissedCallService.sInstance;
47  }
48
49  /**
50   * init
51   *
52   * @param ctx context needed init
53   */
54  public init(ctx: Context) {
55    this.context = ctx;
56    sharedPreferencesUtils.init(ctx);
57    MissedCallNotifier.getInstance().init(ctx);
58    CallLogRepository.getInstance().init(ctx);
59    ContactRepository.getInstance().init(ctx);
60  }
61
62  /**
63   * updateAllMissedCallNotifications
64   */
65  public async updateAllMissedCallNotifications() {
66    HiLog.i(TAG, 'updateMissedCallNotifications');
67    MissedCallNotifier.getInstance().cancelAllNotification();
68    this.sendMissedCallNotify();
69  }
70
71  /**
72   * updateMissedCallNotifications
73   */
74  public async updateMissedCallNotifications() {
75    if (this.lastMissedId == -1) {
76      this.lastMissedId = await this.getLastNotificationId();
77    }
78    this.registerDataChangeObserver();
79    HiLog.i(TAG, 'updateMissedCallNotifications, lastMissedId:' + this.lastMissedId);
80    this.sendMissedCallNotify(this.lastMissedId);
81  }
82
83  /**
84   *
85   * Unread missed call notification
86   */
87  public async unreadCallNotification(map:Map<string,string>) {
88    MissedCallNotifier.getInstance().sendUnreadCallNotification(map);
89  }
90
91  /**
92   * cancelMissedNotificationAction
93   *
94   * @param data MissedCallNotifyData need cancel notify
95   */
96  public async cancelMissedNotificationAction(data: MissedCallNotifyData) {
97    HiLog.i(TAG, `cancelMissedNotificationAction, ${JSON.stringify(data)}`);
98    MissedCallNotifier.getInstance().cancelNotificationById(data.id, data.count);
99    CallLogRepository.getInstance().markMissedCallLogAsRead(data.phoneNumber);
100  }
101
102  /**
103   * cancelAllMissedNotificationAction
104   */
105  public async cancelAllMissedNotificationAction() {
106    let unreadMissed = await MissedCallNotifier.getInstance().getMissedBadgeNumber()
107    if (unreadMissed > 0) {
108      HiLog.i(TAG, `cancelAllMissedNotificationAction cancel all`);
109      MissedCallNotifier.getInstance().cancelAllNotification();
110      CallLogRepository.getInstance().markMissedCallLogAsRead();
111    }
112  }
113
114  private constructor() {
115  }
116
117  private getContext() {
118    if (this.context && this.context != undefined) {
119      return this.context;
120    }
121    return globalThis.context;
122  }
123
124  private registerDataChangeObserver() {
125    HiLog.i(TAG, 'registerDataChangeObserver');
126    CallLogRepository.getInstance().registerDataChangeObserver(this.onCallLogChanged);
127  }
128
129  private unRegisterDataChangeObserver() {
130    HiLog.i(TAG, 'unRegisterDataChangeObserver');
131    CallLogRepository.getInstance().unRegisterDataChangeObserver(this.onCallLogChanged);
132  }
133
134  private async getLastNotificationId() {
135    return <number> await sharedPreferencesUtils.getFromPreferences(LAST_MISSED_ID, -1)
136  }
137
138  private setLastNotificationId(id: number, lastId?: number): boolean {
139    if (!lastId || this.lastMissedId < id) {
140      HiLog.i(TAG, `setLastNotificationId: ${id}`);
141      this.lastMissedId = id;
142      this.unRegisterDataChangeObserver();
143      sharedPreferencesUtils.saveToPreferences(LAST_MISSED_ID, id)
144      return true;
145    }
146    return false;
147  }
148
149  private sendMissedCallNotify(lastId?: number) {
150    CallLogRepository.getInstance().findMissedCallLogUnread((data: CallLog[]) => {
151      HiLog.i(TAG, 'sendMissedCallNotify, callLog unread count:' + data.length);
152      if (data.length <= 0 || !this.setLastNotificationId(data[0].id, lastId)) {
153        HiLog.i(TAG, 'sendMissedCallNotify, No new CallLog.');
154        return;
155      }
156      let phoneNums = new Set();
157      for (let callLog of data) {
158        phoneNums.add(callLog.phoneNumber);
159      }
160      this.queryContactsName(Array.from(phoneNums), (numberMap) => {
161        let missedData: Map<string, MissedCallNotifyData> = new Map();
162        for (let callLog of data) {
163          let displayName = callLog.phoneNumber;
164          if (numberMap.has(callLog.phoneNumber)) {
165            displayName = numberMap.get(callLog.phoneNumber);
166          }
167          if (!missedData.has(displayName)) {
168            missedData.set(displayName, {
169              phoneNumber: callLog.phoneNumber,
170              displayName: displayName,
171              id: callLog.id,
172              createTime: callLog.createTime,
173              count: 1,
174              ringDuration: callLog.ringDuration
175            });
176          } else {
177            missedData.get(displayName).count++;
178          }
179        }
180        MissedCallNotifier.getInstance().updateMissedCallNotifications(missedData);
181      })
182    }, lastId);
183  }
184
185  private queryContactsName(numberList, callback) {
186    if (numberList.length == 0) {
187      HiLog.w(TAG, 'queryContactsName, has no number');
188      callback(new Map());
189      return;
190    }
191    ContactRepository.getInstance().queryContactDataByNumber(numberList, contacts => {
192      // Convert the result to Map, key: mobile number, value: name
193      let numberMap = this.getNumberMap(contacts);
194      callback(numberMap);
195    });
196  }
197
198  private getNumberMap(contacts) {
199    let numberMap = new Map();
200    for (let item of contacts) {
201      if (!StringUtil.isEmpty(item.displayName)) {
202        numberMap.set(item.detailInfo, item.displayName);
203      }
204    }
205    return numberMap;
206  }
207}