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 ICallLogService from './ICallLogService'; 17import { CallLog } from './entity/CallLog'; 18import { CallType } from './entity/CallLog'; 19import { ArrayUtil } from '../../../../../common/src/main/ets/util/ArrayUtil'; 20import MergedCallLog from './entity/MergedCallLog'; 21import { StringUtil } from '../../../../../common/src/main/ets/util/StringUtil'; 22import { MergeRule } from './CallLogSetting'; 23import StringFormatUtil from '../../../../../entry/src/main/ets/util/StringFormatUtil'; 24 25export class CallLogService implements ICallLogService { 26 private static instance: CallLogService; 27 private mergeRule: MergeRule; 28 private context: Context; 29 30 private constructor() { 31 } 32 33 /* 34 * init if Call From serviceAbility globalThis.context is Null 35 *@param ctx Context used for dataShare 36 */ 37 init(ctx: Context) { 38 this.context = ctx; 39 } 40 41 public static getInstance(): CallLogService { 42 if (!CallLogService.instance) { 43 CallLogService.instance = new CallLogService(); 44 } 45 return CallLogService.instance; 46 } 47 48 setMergeRule(mergeRule: MergeRule) { 49 this.mergeRule = mergeRule; 50 } 51 52 mergeCallLogs(callLogs: CallLog[]) { 53 if (this.getMergeRule() === MergeRule.CONTACT) { 54 return this.mergeByContact(callLogs); 55 } else { 56 return this.mergeByTime(callLogs); 57 } 58 } 59 60 mergeMissedCalls(callLogs: CallLog[]) { 61 let mergeRule = this.getMergeRule(); 62 let callLogList: CallLog[] = []; 63 let missedList: CallLog[] = []; 64 for (let callLog of callLogs) { 65 callLogList.push(callLog); 66 if (callLog.callType == CallType.MISSED || 67 callLog.callType == CallType.REJECTED) { 68 //Filtering Missed Call Data 69 missedList.push(callLog); 70 let timeList = []; 71 // Filtering by Contact Missed Calls and Redialing 72 if (mergeRule === MergeRule.CONTACT) { 73 for (let k = 0; k < missedList.length; k++) { 74 let missedPhone = missedList[k].phoneNumber; 75 for (let i = 0; i < callLogList.length; i++) { 76 let allSpecialPhone = callLogList[i].phoneNumber; 77 if (missedPhone == allSpecialPhone) { 78 let timeNumber = callLogList[i].createTime; 79 let obj = { 80 'id': i, 81 'timeObj': timeNumber, 82 }; 83 timeList.push(obj); 84 let max = timeList[0].timeObj; 85 for (let j = 0; j < timeList.length; j++) { 86 if (timeList[j].timeObj > max) { 87 max = timeList[j].timeObj; 88 let n = timeList[j].id; 89 if (callLogList[n].callType == CallType.OUT) { 90 missedList.splice(k, 1); 91 } 92 } else { 93 let m = timeList[0].id; 94 if (callLogList[m].callType == CallType.OUT) { 95 missedList.splice(k, 1); 96 } 97 } 98 } 99 } 100 } 101 } 102 } 103 } 104 } 105 if (mergeRule === MergeRule.CONTACT) { 106 return this.mergeByContact(missedList); 107 } else { 108 return this.mergeByTime(missedList); 109 } 110 } 111 112 /** 113 * In the case of merging by time, the post-processing of the call record 114 * service data is optimized based on the original call record data. 115 * @param callLogList 116 * @return 117 */ 118 private mergeByTime(callLogList: CallLog[]) { 119 let resultList = []; 120 if (ArrayUtil.isEmpty(callLogList)) { 121 return resultList; 122 } 123 // Call records are cached from the first record. 124 let tempElement = new MergedCallLog(callLogList[0]); 125 // Indicates the creation time of the latest record. 126 // After call records are merged, the time is displayed. 127 let tempCallTime = callLogList[0].createTime; 128 // Type of the call record that retains the latest record. 129 // This type is displayed after the call record is combined. 130 let tempCallType = callLogList[0].callType; 131 let num = 1; 132 let ids = []; 133 ids.push(callLogList[0].id); 134 for (let i = 1; i < callLogList.length; i++) { 135 let element = callLogList[i]; 136 // Whether the cached field needs to be combined with the current field 137 if (this.callLogMergeCheck(tempElement, element)) { 138 num++; 139 // Put the latest record ID into the merged array. 140 ids.push(element.id); 141 } else { 142 //If the latest data is inconsistent with the cached data, 143 // replace the num and ids data in the cached data and 144 // save the cached data to the result set. 145 tempElement.count = num; 146 tempElement.ids = ids; 147 // Displays the creation time of the latest saved record. 148 tempElement.createTime = this.formatTime(tempCallTime); 149 tempElement.callType = tempCallType; 150 resultList.push(tempElement); 151 /* Reset num and ids to the latest count and record, 152 and reset tempCallTime to the latest creation time of the next record.*/ 153 num = 1; 154 ids = []; 155 tempCallTime = element.createTime; 156 tempCallType = element.callType; 157 ids.push(element.id); 158 } 159 tempElement = new MergedCallLog(element); 160 } 161 /* Put the last piece of cached data into the result set*/ 162 if (tempElement != null) { 163 tempElement.count = num; 164 tempElement.ids = ids; 165 tempElement.createTime = this.formatTime(tempCallTime); 166 tempElement.callType = tempCallType; 167 resultList.push(tempElement); 168 } 169 return resultList; 170 } 171 172 /** 173 * In the case of merging by contact, the post-processing 174 * of the call record service data is optimized based on the original call record data. 175 * 176 * @param {Array} callLogList 177 * @return {Array} callLogList 178 */ 179 private mergeByContact(callLogs: CallLog[]) { 180 let resultList = []; 181 if (ArrayUtil.isEmpty(callLogs)) { 182 return resultList; 183 } 184 let contactTempMap = new Map(); 185 let phoneNumberMap = new Map(); 186 for (let i = 0; i < callLogs.length; i++) { 187 let element = new MergedCallLog(callLogs[i]); 188 element.createTime = this.formatTime(callLogs[i].createTime); 189 // In the case of merging by contact, the combined record entry is fixed to 1. 190 element.count = 1; 191 // In the case of merging by contact, the IDs of 192 // the merging record are fixed to the ID of the record. 193 element.ids = [callLogs[i].id]; 194 // Call records without contacts are combined by phone number. 195 if (StringUtil.isEmpty(element.quickSearchKey)) { 196 if (!phoneNumberMap.has(element.phoneNumber)) { 197 resultList.push(element); 198 phoneNumberMap.set(element.phoneNumber, callLogs[i].phoneNumber); 199 } 200 } else { // Call records with contacts are merged by contact. 201 let isContactKey = contactTempMap.has(element.quickSearchKey); 202 if (!isContactKey) { 203 resultList.push(element); 204 contactTempMap.set(element.quickSearchKey, callLogs[i].quickSearchKey); 205 } 206 } 207 } 208 return resultList; 209 } 210 211 /** 212 * Obtain the call time. 213 * 214 * @param date Call record creation timestamp 215 * @return {object} Talk time 216 */ 217 private formatTime(date) { 218 let result; 219 // If the value is not a number, the value is not parsed. 220 if (isNaN(date)) { 221 return date; 222 } 223 let timestamp = parseInt(date) * 1000; 224 let callTime = new Date(timestamp); 225 let now = new Date(); 226 if (callTime.getTime() > now.getTime()) { 227 result = callTime.getFullYear() + '/' + (callTime.getMonth() + 1) + '/' + callTime.getDate(); 228 } else if (callTime.getFullYear() == now.getFullYear()) { 229 if (callTime.getMonth() == now.getMonth()) { 230 // Same month of the same year 231 let timeDiff = parseInt(((now.getTime() - callTime.getTime()) / 60000).toString()); 232 let dayDiff = now.getDate() - callTime.getDate(); 233 let hour = callTime.getHours().toString(); 234 let minutes = callTime.getMinutes() < 10 ? '0' + callTime.getMinutes() : callTime.getMinutes().toString(); 235 if (dayDiff == 0) { 236 // 同天 237 if (timeDiff == 0) { 238 result = $r("app.string.justNow"); 239 } else if (timeDiff < 60) { 240 result = $r("app.string.minutesAgo", timeDiff); 241 } else { 242 let timeDetail: any = {}; 243 if (parseInt(StringFormatUtil.judgeSysTime(this.context)) == 12) { 244 timeDetail.time = StringFormatUtil.getDayMessage(hour, minutes); 245 } else { 246 timeDetail.time = $r("app.string.time_normal", hour, minutes); 247 } 248 result = timeDetail.time 249 } 250 } else if (dayDiff == 1) { 251 result = $r("app.string.yesterday"); 252 } else { 253 result = (callTime.getMonth() + 1) + '/' + callTime.getDate(); // 'MM/dd' 254 } 255 } else { 256 result = (callTime.getMonth() + 1) + '/' + callTime.getDate(); 257 } 258 } else { 259 // 'yyyy/MM/dd' 260 result = callTime.getFullYear() + '/' + (callTime.getMonth() + 1) + '/' + callTime.getDate(); 261 } 262 return result; 263 } 264 265 /** 266 * Checks whether two call records need to be combined when the call records are combined by time. 267 * If the call records need to be combined, true is returned. Otherwise, false is returned. 268 * 269 * @param oldElement Call records before merging 270 * @param newElement Combined call records 271 * @return 272 */ 273 private callLogMergeCheck(oldElement: MergedCallLog, newElement: CallLog) { 274 /* Merge Rules: 275 1. The phone numbers are combined only when the phone numbers are the same. 276 2. If the number is the same and the call type is 1, 2, 3, or 5, 277 the call is combined. Types 1, 2, 3, and 5 are not combined. 278 */ 279 if (oldElement.phoneNumber.trim() == newElement.phoneNumber.trim()) { 280 if (oldElement.callType == CallType.IN || oldElement.callType == CallType.OUT) { 281 if (newElement.callType == CallType.IN || newElement.callType == CallType.OUT) { 282 return true; 283 } 284 return false; 285 } 286 if (newElement.callType == CallType.MISSED || newElement.callType == CallType.REJECTED) { 287 return true; 288 } 289 } 290 return false; 291 } 292 293 private getMergeRule() { 294 return this.mergeRule; 295 } 296}