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 ConversationModel from '../model/ConversationModel';
17import common from '../data/commonData';
18import HiLog from '../utils//HiLog';
19import ConversationListService from './ConversationListService';
20import telephoneUtils from '../utils/TelephoneUtil';
21import ContactsService from './ContactsService';
22import LooseObject from '../data/LooseObject';
23
24const TAG: string = 'ConversationService';
25
26export default class ConversationService {
27  private static instance: ConversationService;
28  private conversationModel: ConversationModel = new ConversationModel();
29
30  private constructor() {
31  }
32
33  public static getInstance(): ConversationService {
34    if (ConversationService.instance == null) {
35      ConversationService.instance = new ConversationService();
36    }
37    return ConversationService.instance;
38  }
39
40  public insertSmsMmsInfo(valueBucket, callback, context): void {
41    let mmsContext = context ? context : globalThis.mmsContext;
42    if (globalThis.DataWorker != null) {
43      globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.insertSmsMmsInfo, {
44        valueBucket: valueBucket,
45        context: mmsContext
46      }, res => {
47        if (callback) {
48          callback(res);
49        }
50      });
51    } else {
52      this.conversationModel.insertSmsMmsInfo(valueBucket, callback, mmsContext);
53    }
54  }
55
56  public deleteSmsMmsInfoByCondition(actionData, callback, context): void {
57    let mmsContext = context ? context : globalThis.mmsContext;
58    if (globalThis.DataWorker != null) {
59      globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.deleteSmsMmsInfoByCondition, {
60        actionData: actionData,
61        context: mmsContext
62      }, res => {
63        if (callback) {
64          callback(res);
65        }
66      });
67    } else {
68      this.conversationModel.deleteSmsMmsInfoByCondition(actionData, callback, mmsContext);
69    }
70  }
71
72  public updateSmsMmsInfoByCondition(actionData, valueBucket, callback, context): void {
73    let mmsContext = context ? context : globalThis.mmsContext;
74    if (globalThis.DataWorker != null) {
75      globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.updateSmsMmsInfoByCondition, {
76        actionData: actionData,
77        valueBucket: valueBucket,
78        context: mmsContext
79      }, res => {
80        if (callback) {
81          callback(res);
82        }
83      });
84    } else {
85      this.conversationModel.updateSmsMmsInfoByCondition(actionData, valueBucket, callback, mmsContext);
86    }
87  }
88
89  public querySmsMmsInfoByCondition(actionData, callback, context): void {
90    let mmsContext = context ? context : globalThis.mmsContext;
91    if (globalThis.DataWorker != null) {
92      globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.querySmsMmsInfoByCondition, {
93        actionData: actionData,
94        context: mmsContext
95      }, res => {
96        callback(res);
97      });
98    } else {
99      this.conversationModel.querySmsMmsInfoByCondition(actionData, callback, mmsContext);
100    }
101  }
102
103  public querySmsMmsInfoSizeByCondition(actionData, callback, context): void {
104    let mmsContext = context ? context : globalThis.mmsContext;
105    if (globalThis.DataWorker != null) {
106      globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.querySmsMmsInfoSizeByCondition, {
107        actionData: actionData,
108        context: mmsContext
109      }, res => {
110        callback(res);
111      });
112    } else {
113      this.conversationModel.querySmsMmsInfoSizeByCondition(actionData, callback, mmsContext);
114    }
115  }
116
117  public statisticalData(callback, context) {
118    let mmsContext = context ? context : globalThis.mmsContext;
119    if (globalThis.DataWorker != null) {
120      globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.statisticalData, {
121        context: mmsContext
122      }, res => {
123        callback(res);
124      });
125    } else {
126      this.conversationModel.statisticalData(callback, mmsContext);
127    }
128  }
129
130  public queryMaxGroupId(callback, context): void {
131    let mmsContext = context ? context : globalThis.mmsContext;
132    if (globalThis.DataWorker != null) {
133      globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.queryMaxGroupId, {
134        context: mmsContext
135      }, res => {
136        callback(res);
137      });
138    } else {
139      this.conversationModel.queryMaxGroupId(callback, mmsContext);
140    }
141  }
142
143  public queryMessageDetail(actionData, callback, context): void {
144    this.querySmsMmsInfoByCondition(actionData, res => {
145      let result: LooseObject = {};
146      result.code = res.code;
147      if (res.code == common.int.SUCCESS) {
148        this.groupDetailMessage(res.abilityResult, actionData, resultList => {
149          result.response = resultList;
150          callback(result);
151        });
152      } else {
153        HiLog.w(TAG, 'queryMessageDetail, failed');
154        callback(result);
155      }
156    }, context);
157  }
158
159  public convertConversationList(mmsList): Array<LooseObject> {
160    let resultList: Array<LooseObject> = [];
161    for (let item of mmsList) {
162      let result: LooseObject = {};
163      result.id = item.msgId;
164      result.content = item.msgContent;
165      result.isLock = item.isLock == 0 ? false : true;
166      result.isStared = item.isCollect == 0 ? false : true;
167      // Fields related to MMS messages
168      result.msgType = 0;
169      result.isFullScreenImg = true;
170      result.read = item.isRead == 0 ? '0' : '1';
171      if (item.msgState == 0) {
172        result.sendStatus = common.int.SEND_MESSAGE_SUCCESS;
173      } else if (item.msgState == 2) {
174        result.sendStatus = common.int.SEND_MESSAGE_FAILED;
175      } else if (item.msgState == 1) {
176        result.sendStatus = common.int.SEND_MESSAGE_SENDING;
177      } else {
178        result.sendStatus = common.int.SEND_DRAFT;
179      }
180      result.subId = item.slotId;
181      result.timeMillisecond = item.startTime;
182      result.isMsm = item.msgType == 0 ? false : true;
183      result.isCbChecked = false;
184      result.isDraft = false;
185      // Fields related to group-sending
186      result.groupId = item.groupId;
187      // Determine whether it is the receiver or sender.
188      result.isReceive = item.isSender == 0 ? false : true;
189      result.date = common.string.EMPTY_STR;
190      result.time = common.string.EMPTY_STR;
191      result.completeNumber = 0;
192      result.failuresNumber = 0;
193      result.hasReport = item.isSendReport == 0 ? false : true;
194      if (!result.isMsm) {
195        result.msgShowType = common.MESSAGE_SHOW_TYPE.NORMAL;
196      }
197      resultList.push(result);
198    }
199    return resultList;
200  }
201
202  public groupDetailMessage(mmsList, actionData, callback): void {
203    let details: Array<LooseObject> = this.convertConversationList(mmsList);
204    if (actionData.contactsNum == 1) {
205      callback(details);
206      return;
207    }
208    let resultList: Array<LooseObject> = [];
209    // Group by groupId.
210    let detailMap = this.convertConversationMap(details);
211    // By group
212    let groupIds = detailMap.keys();
213    for (let groupId of groupIds) {
214      let groups = detailMap.get(groupId);
215      let result: LooseObject = groups[0];
216      let failuresNumber = 0;
217      let completeNumber = 0;
218      for (let item of groups) {
219        if (item.sendStatus == common.int.SEND_MESSAGE_FAILED) {
220          failuresNumber++;
221        }
222        if (item.sendStatus == common.int.SEND_MESSAGE_FAILED || item.sendStatus == common.int.SEND_MESSAGE_SUCCESS) {
223          completeNumber++;
224        }
225      }
226      result.completeNumber = completeNumber;
227      result.failuresNumber = failuresNumber;
228      resultList.push(result);
229    }
230    callback(resultList);
231  }
232
233  public convertConversationMap(details) {
234    let conversationMap = new Map();
235    // grouping
236    for (let element of details) {
237      if (conversationMap.has(element.groupId)) {
238        let groups = conversationMap.get(element.groupId);
239        groups.push(element);
240      } else {
241        let groups = [];
242        groups.push(element);
243        conversationMap.set(element.groupId, groups);
244      }
245    }
246    return conversationMap;
247  }
248
249  public insertSessionAndDetail(actionData, callback, context): void {
250    let sendResults: Array<LooseObject> = actionData.sendResults;
251    let isReceive: boolean = actionData.isReceive;
252    if (sendResults.length == 0) {
253      HiLog.w(TAG, 'insertSessionAndDetail, sendResults.length == 0');
254      return;
255    }
256    let param: LooseObject = this.dealSendResults(sendResults);
257    // Check whether a session list has been created.
258    ConversationListService.getInstance().querySessionByTelephone(param.telephone, res => {
259      let response: LooseObject = res.response;
260      if (res.code == common.int.SUCCESS && response.id <= 0) {
261        this.dealNoExistSession(isReceive, param, actionData, callback, context);
262      } else {
263        this.dealExistSession(response, param, actionData, callback, context);
264      }
265    }, context);
266  }
267
268  public dealNoExistSession(isReceive, param, actionData, callback, context): void {
269    let unreadCount: number = 0;
270    if (isReceive) {
271      unreadCount = 1;
272    }
273    let valueBucket: LooseObject = {
274      'telephone': param.telephone,
275      'content': param.content,
276      'contacts_num': param.contactsNum,
277      'sms_type': param.smsType,
278      'unread_count': unreadCount,
279      'sending_status': param.sendStatus,
280      'has_draft': common.has_draft.NO,
281      'time': param.timestamp,
282      'message_count': 1,
283      'has_mms': actionData.isMms ? common.has_mms.HAVE : common.has_mms.NO,
284      'has_attachment': actionData.hasAttachment ? common.has_attachment.HAVE : common.has_attachment.NO
285    }
286    ConversationListService.getInstance().insertSession(valueBucket, sessionResult => {
287      HiLog.i(TAG, 'dealNoExistSession, insertSession callback');
288      if (sessionResult.code == common.int.FAILURE) {
289        HiLog.e(TAG, 'dealNoExistSession, insertSession fail!');
290        callback(sessionResult);
291        return;
292      }
293      // Invoke the SMS database to insert SMS messages.
294      this.dealInsertMessageDetail(param, actionData, sessionResult.abilityResult, res => {
295        let result: LooseObject = {
296          'rowId': sessionResult.abilityResult,
297          'initDatas': res.initDatas,
298          'groupId': res.groupId
299        }
300        callback(result);
301      }, context);
302    }, context);
303  }
304
305  public dealExistSession(response, param, actionData, callback, context): void {
306    let unreadCount: number = 0;
307    if (actionData.isReceive) {
308      unreadCount = response.unreadCount + 1;
309    }
310    let hasDraft: number = response.hasDraft;
311    if (actionData.hasDraft && hasDraft == 1) {
312      hasDraft = 0;
313    }
314    let valueBucket: LooseObject = {
315      'content': param.content,
316      'unread_count': unreadCount,
317      'time': new Date().getTime(),
318      'sending_status': param.sendStatus,
319      'has_draft': hasDraft,
320      'message_count': (response.messageCount + 1),
321      'has_attachment': actionData.hasAttachment ? 1 : 0,
322      'has_mms': actionData.isMms ? 1 : 0,
323    }
324    let sessionId: number = response.id;
325    let condition: LooseObject = {};
326    condition.threadId = sessionId;
327    ConversationListService.getInstance().updateSessionByCondition(condition, valueBucket, res => {
328      // Invoke the SMS database to insert SMS messages.
329      this.dealInsertMessageDetail(param, actionData, sessionId, res => {
330        let result: LooseObject = {
331          'rowId': sessionId,
332          'initDatas': res.initDatas,
333          'groupId': res.groupId
334        }
335        callback(result);
336      }, context);
337    }, context);
338  }
339
340  public dealInsertMessageDetail(param, actionData, threadId, callback, context): void {
341    this.queryMaxGroupId(res => {
342      let maxGroupId: number = 0;
343      if (res.code == common.int.SUCCESS) {
344        maxGroupId = Number(res.abilityResult) + 1;
345      } else {
346        HiLog.w(TAG, 'dealInsertMessageDetail, queryMaxGroupId failed');
347        callback(res);
348      }
349      this.insertMessageDetailByMaxGroupId(param, actionData, threadId, maxGroupId, result => {
350        callback(result);
351      }, context);
352    }, context);
353  }
354
355  private insertMessageDetailByMaxGroupId(param, actionData, threadId, maxGroupId, callback, context): void {
356    let count: number = 0;
357    let initDatas: Array<LooseObject> = [];
358    let result: LooseObject = {};
359    result.groupId = maxGroupId;
360    let sendResults = actionData.sendResults;
361    sendResults.forEach(sendResult => {
362      let valueBucket: LooseObject = this.buildInsertSmsMmsRow(param, actionData, sendResult, threadId, maxGroupId);
363      this.insertSmsMmsInfo(valueBucket, res => {
364        count++;
365        let initData: LooseObject = {
366          'id': res.abilityResult,
367          'telephone': sendResult.telephone
368        };
369        initDatas.push(initData);
370        if (count == actionData.sendResults.length) {
371          result.initDatas = initDatas;
372          callback(result);
373        }
374      }, context);
375    });
376  }
377
378  private buildInsertSmsMmsRow(param, actionData, sendResult, threadId, maxGroupId): LooseObject {
379    let valueBucket: LooseObject = {
380      'slot_id': common.int.SIM_ONE,
381      'receiver_number': common.string.EMPTY_STR,
382      'sender_number': common.string.EMPTY_STR,
383      'start_time': common.string.EMPTY_STR,
384      'end_time': common.string.EMPTY_STR,
385      'msg_type': common.MESSAGE_TYPE.NORMAL,
386      'sms_type': common.sms_type.COMMON,
387      'msg_title': common.string.EMPTY_STR,
388      'msg_content': common.string.EMPTY_STR,
389      'msg_state': common.int.SEND_MESSAGE_SENDING,
390      'operator_service_number': common.string.EMPTY_STR,
391      'msg_code': common.string.EMPTY_STR,
392      'session_id': threadId,
393      'is_lock': common.is_lock.NO,
394      'is_read': common.is_read.UN_READ,
395      'is_collect': common.is_collect.NOT_FAVORITE,
396      'session_type': common.session_type.COMMON,
397      'is_sender': common.is_sender.NO,
398      'group_id': maxGroupId,
399      'is_send_report': common.is_send_report.NO
400    };
401    if (sendResult.slotId != null) {
402      valueBucket.slot_id = sendResult.slotId;
403    }
404    if (actionData.isReceive) {
405      valueBucket.receiver_number = actionData.ownNumber ? actionData.ownNumber : common.string.EMPTY_STR;
406      valueBucket.sender_number = sendResult.telephone ? sendResult.telephone : common.string.EMPTY_STR;
407      valueBucket.is_read = common.is_read.UN_READ;
408    } else {
409      valueBucket.receiver_number = sendResult.telephone ? sendResult.telephone : common.string.EMPTY_STR;
410      valueBucket.sender_number = actionData.ownNumber ? actionData.ownNumber : common.string.EMPTY_STR;
411      valueBucket.is_read = common.is_read.READ;
412    }
413    let time: string = sendResult.time;
414    if (time == null) {
415      time = new Date().getTime() + common.string.EMPTY_STR;
416    }
417    valueBucket.start_time = time;
418    valueBucket.end_time = time;
419    if (param.isMms != null && param.isMms) {
420      valueBucket.msg_type = common.MESSAGE_TYPE.THEME;
421    }
422    if (param.smsType != null) {
423      valueBucket.sms_type = param.smsType;
424    }
425    if (param.content != null) {
426      valueBucket.msg_title = param.content;
427      valueBucket.msg_content = param.content;
428    }
429    if (sendResult.sendStatus != null) {
430      valueBucket.msg_state = sendResult.sendStatus;
431    }
432    if (actionData.isSender != null) {
433      valueBucket.is_sender = actionData.isSender;
434    }
435    if (actionData.hasReport != null) {
436      valueBucket.is_send_report = actionData.hasReport;
437    }
438    return valueBucket;
439  }
440
441  public dealSendResults(sendResults: Array<LooseObject>): LooseObject {
442    let contactsNum: number = sendResults.length;
443    let telephone: string = common.string.EMPTY_STR;
444    let content: string = common.string.EMPTY_STR;
445    let sendStatus: number = common.int.SEND_MESSAGE_SUCCESS;
446    let failSum: number = 0;
447    let time: string = common.string.EMPTY_STR;
448    for (let sendResult of sendResults) {
449      telephone = telephone + sendResult.telephone + common.string.COMMA;
450      content = sendResult.content;
451      sendStatus = sendResult.sendStatus;
452      if (sendResult.sendStatus == common.int.SEND_MESSAGE_FAILED) {
453        failSum++;
454      }
455      if (sendResult.time != null) {
456        time = sendResult.time;
457      }
458      if (sendResult.isMessageSim != null && sendResult.isMessageSim) {
459        telephone = sendResult.telephone;
460        contactsNum = 1;
461      }
462    }
463    // If it fails, then the session list turns out to be a failure.
464    if (failSum > 0) {
465      sendStatus = common.int.SEND_MESSAGE_FAILED;
466    }
467    telephone = telephone.substring(0, telephone.length - 1);
468    let smsType: number = common.sms_type.COMMON;
469    if (contactsNum == 1 && telephoneUtils.judgeIsInfoMsg(telephone)) {
470      smsType = common.sms_type.NOTICE;
471    }
472    let result: LooseObject = {};
473    let timestamp: number = new Date().getTime();
474    result.contactsNum = contactsNum;
475    result.content = content;
476    result.telephone = telephoneUtils.dealTelephoneSort(telephone);
477    result.sendStatus = sendStatus;
478    result.timestamp = time != common.string.EMPTY_STR ? time : timestamp;
479    result.smsType = smsType;
480    return result;
481  }
482
483  public updateSessionAndDetail(actionData): void {
484    let sendResults: Array<LooseObject> = actionData.sendResults;
485    if (sendResults.length == 0) {
486      return;
487    }
488    let param: LooseObject = this.dealSendResults(sendResults);
489    // Update the status of the session list.
490    let valueBucket: LooseObject = {
491      'sending_status': param.sendStatus,
492      'content': param.content
493    }
494    ConversationListService.getInstance().updateSessionByCondition(actionData, valueBucket, null, null);
495    // Update the status of the information list.
496    for (let sendResult of sendResults) {
497      actionData.sendStatus = sendResult.sendStatus;
498      if (sendResult.sendStatus == common.int.SEND_MESSAGE_FAILED) {
499        actionData.sendStatus = common.int.SEND_MESSAGE_FAILED;
500      } else if (sendResult.sendStatus == common.int.SEND_MESSAGE_SUCCESS) {
501        actionData.sendStatus = common.int.SEND_MESSAGE_SUCCESS;
502      } else {
503        actionData.sendStatus = common.int.SEND_MESSAGE_SENDING;
504      }
505      actionData.msgId = sendResult.id;
506      let valueBucket: LooseObject = {
507        'msg_state': actionData.sendStatus,
508      };
509      this.updateSmsMmsInfoByCondition(actionData, valueBucket, null, null);
510    }
511  }
512
513  public saveImage(actionData, callback, context): void {
514    callback('saveImage function to be developed!');
515  }
516
517  public gotoShare(actionData, callback, context): void {
518    callback('gotoShare function to be developed!');
519  }
520
521  public dealContactsName(telephones, actionData, resultList, callback, context): void {
522    actionData.telephones = telephones;
523    actionData.hasDelete = '0';
524    if (telephones.length == 0) {
525      callback(resultList);
526    }
527    ContactsService.getInstance().queryContactDataByCondition(actionData, res => {
528      let groupIdMap: LooseObject = {};
529      if (res.code == common.int.FAILURE || res.abilityResult.length == 0) {
530        groupIdMap = this.convertingMessageByGroup(resultList);
531      } else {
532        // Convert the result to Map, key: mobile number, value: name
533        let telephoneMap = new Map();
534        let contacts: Array<LooseObject> = res.abilityResult;
535        for (let item of contacts) {
536          if (item.displayName == common.string.EMPTY_STR) {
537            telephoneMap.set(item.detailInfo, item.detailInfo);
538          } else {
539            telephoneMap.set(item.detailInfo, item.displayName);
540          }
541        }
542        // Match the result based on the mobile number.
543        groupIdMap = this.getGroupIdMap(resultList, telephoneMap);
544      }
545      let lists = [];
546      let favoriteList = [];
547      for (let key of Object.keys(groupIdMap)) {
548        let value = groupIdMap[key];
549        let contactsNums = value.telephone.split(common.string.COMMA);
550        value.contactsNum = contactsNums.length;
551        if (value.isFavorite) {
552          let temp = JSON.parse(JSON.stringify(value));
553          temp.icon = '/common/icon/icon_favorite.svg';
554          favoriteList.push(temp);
555          value.isFavorite = false;
556        }
557        lists.push(value);
558      }
559      // Add favorite data to the search list.
560      if (favoriteList.length > 0) {
561        lists = lists.concat(favoriteList);
562      }
563      callback(lists);
564    }, context);
565  }
566
567  public getGroupIdMap(resultList, telephoneMap): LooseObject {
568    let groupIdMap: LooseObject = {};
569    for (let map of resultList) {
570      // Indicates the combination of multiple names. The names need to be displayed in combination.
571      if (telephoneMap.has(map.telephone)) {
572        map.name = telephoneMap.get(map.telephone);
573        map.nameFormatter = map.name + '<' + map.telephoneFormat + '>';
574      } else {
575        map.nameFormatter = map.telephoneFormat;
576      }
577      if (Object.keys(groupIdMap).indexOf(map.groupId) > -1) {
578        let list = groupIdMap[map.groupId];
579        list.name = list.name + common.string.COMMA + map.name;
580        list.nameFormatter = list.nameFormatter + common.string.COMMA + map.nameFormatter;
581        list.telephone = list.telephone + common.string.COMMA + map.telephone;
582        list.telephoneFormat = list.telephoneFormat + common.string.COMMA + map.telephoneFormat;
583      } else {
584        groupIdMap[map.groupId] = map;
585      }
586    }
587    return groupIdMap;
588  }
589
590  public convertingMessageByGroup(resultList): LooseObject {
591    let groupIdMap: LooseObject = {};
592    for (let element of resultList) {
593      if (Object.keys(groupIdMap).indexOf(element.groupId) == -1) {
594        groupIdMap[element.groupId] = element;
595      }
596    }
597    return groupIdMap;
598  }
599
600  public statisticsUnreadNotify(callback, context): void {
601    let actionData: LooseObject = {};
602    actionData.hasRead = common.is_read.UN_READ;
603    actionData.smsType = common.sms_type.NOTICE;
604    this.querySmsMmsInfoSizeByCondition(actionData, res => {
605      if (res.code == common.int.SUCCESS) {
606        callback(res.abilityResult);
607      } else {
608        HiLog.w(TAG, 'statisticsUnreadNotify fail!');
609        callback(0);
610      }
611    }, context);
612  }
613}