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 dataShare from '@ohos.data.dataShare'; 17import dataSharePredicates from "@ohos.data.dataSharePredicates"; 18import IContactRepository from './IContactRepository'; 19import Contact from '../entity/Contact'; 20import RawContact from '../entity/RawContact'; 21import ContactBuilder from '../entity/ContactBuilder'; 22import { DataItem } from '../entity/DataItem'; 23import ContactList from './ContactList'; 24import DAOperation from './DAOperation'; 25import ContactDelta from './ContactDelta'; 26import { RawContacts } from '../contract/RawContacts'; 27import { Contacts } from '../contract/Contacts'; 28import { Data } from '../contract/Data'; 29import { HiLog } from '../../../../../../common/src/main/ets/util/HiLog'; 30import ContactListItem from './ContactListItem'; 31import { DataItemType } from '../contract/DataType'; 32 33const TAG = "ContactRepository"; 34 35/** 36 * Contact storage management, shielding dependency on the CP layer 37 * Contacts Only 38 */ 39export class ContactRepository implements IContactRepository { 40 static readonly RAW_CONTACT_URL: string = RawContacts.CONTENT_URI; 41 private dataShareHelper; 42 private static instance: ContactRepository; 43 private context: Context 44 45 private constructor() { 46 } 47 48 /* 49 * init if Call From serviceAbility globalThis.context is Null 50 *@param ctx Context used for dataShare 51 */ 52 init(ctx: Context) { 53 this.context = ctx; 54 } 55 56 public static getInstance(): ContactRepository { 57 if (!ContactRepository.instance) { 58 ContactRepository.instance = new ContactRepository(); 59 } 60 return ContactRepository.instance; 61 } 62 63 saveTest() { 64 return false; 65 } 66 67 private async getDataAbilityHelper() { 68 if (this.dataShareHelper == undefined) { 69 this.dataShareHelper = await dataShare.createDataShareHelper(this.context ? this.context : globalThis.context, 70 Contacts.CONTENT_URI); 71 } 72 return this.dataShareHelper; 73 } 74 75 save(contact: ContactDelta, callback) { 76 this.getDataAbilityHelper().then((dataAbilityHelper) => { 77 let opts = contact.buildDiff(); 78 return dataAbilityHelper.executeBatch(ContactRepository.RAW_CONTACT_URL, opts) 79 .then(resultSet => { 80 callback(resultSet); 81 }) 82 .catch(error => { 83 HiLog.w(TAG, 'save error:%s' + JSON.stringify(error.message)); 84 callback(); 85 }); 86 }).catch(error => { 87 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 88 callback(); 89 }); 90 } 91 92 findById(id: number, callback) { 93 if (id < 0) { 94 HiLog.w(TAG, 'findById: id is invalid.'); 95 callback(); 96 return; 97 } 98 this.getDataAbilityHelper().then((dataAbilityHelper) => { 99 let conditionArgs = new dataSharePredicates.DataSharePredicates(); 100 conditionArgs.equalTo(RawContacts.CONTACT_ID, id).orderByAsc(Data.RAW_CONTACT_ID); 101 dataAbilityHelper.query(Data.CONTENT_URI, conditionArgs, null).then(resultSet => { 102 if (resultSet == undefined || !resultSet.goToFirstRow()) { 103 HiLog.w(TAG, 'findById not found.'); 104 callback(); 105 return; 106 } 107 let contactBuilder = ContactBuilder.fromResultSet(resultSet); 108 let currentRawContactId = -1; 109 let rawContact: RawContact = null; 110 do { 111 let rawContactId = resultSet.getLong(resultSet.getColumnIndex(Data.RAW_CONTACT_ID)); 112 if (rawContactId != currentRawContactId) { 113 currentRawContactId = rawContactId; 114 rawContact = RawContact.fromResultSet(resultSet); 115 contactBuilder.rowContacts.push(rawContact); 116 } 117 if (rawContact != undefined) { 118 rawContact.dataItems.push(DataItem.fromResultSet(resultSet)); 119 } 120 } while (resultSet.goToNextRow()); 121 resultSet.close(); 122 callback(contactBuilder.buildContact()); 123 }).catch(error => { 124 HiLog.e(TAG, 'findById error:%s' + JSON.stringify(error.message)); 125 callback(); 126 }); 127 }).catch(error => { 128 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 129 callback(); 130 }); 131 } 132 133 findAll(actionData, callback) { 134 let conditionArgs = new dataSharePredicates.DataSharePredicates(); 135 let offset = actionData.page < 3 ? (actionData.page - 1) * 50 : (actionData.page - 2) * 500 + 50; 136 conditionArgs.limit(actionData.limit, offset) 137 // conditionArgs.limitAs(actionData.limit); 138 // conditionArgs.offsetAs(offset); 139 conditionArgs.equalTo(RawContacts.IS_DELETED, '0').orderByAsc(RawContacts.SORT_FIRST_LETTER); 140 this.getDataAbilityHelper().then((dataAbilityHelper) => { 141 dataAbilityHelper.query(Contacts.CONTACT_URI, conditionArgs, ContactListItem.COLUMNS) 142 .then(resultSet => { 143 let rst: ContactListItem[] = []; 144 if (resultSet.rowCount === 0) { 145 resultSet.close(); 146 callback(rst); 147 } else { 148 resultSet.goToFirstRow(); 149 do { 150 rst.push(new ContactListItem(resultSet)); 151 } while (resultSet.goToNextRow()); 152 resultSet.close(); 153 callback(rst); 154 } 155 }) 156 .catch(error => { 157 HiLog.w(TAG, 'findAll error:%s' + JSON.stringify(error.message)); 158 callback([]); 159 }); 160 }).catch(error => { 161 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 162 callback([]); 163 }); 164 } 165 166 findByQuickSearchKey(searchKey: string, callback) { 167 this.getDataAbilityHelper().then((dataAbilityHelper) => { 168 let conditionArgs = new dataSharePredicates.DataSharePredicates(); 169 conditionArgs.equalTo(Contacts.QUICK_SEARCH_KEY, searchKey).orderByAsc(Data.RAW_CONTACT_ID); 170 dataAbilityHelper.query(Data.CONTENT_URI, conditionArgs, null).then(resultSet => { 171 if (resultSet == undefined || !resultSet.goToFirstRow()) { 172 HiLog.w(TAG, 'findByQuickSearchKey not found.'); 173 callback(); 174 return; 175 } 176 let contactBuilder = ContactBuilder.fromResultSet(resultSet); 177 let currentRawContactId = -1; 178 let rawContact: RawContact = null; 179 do { 180 let rawContactId = resultSet.getLong(resultSet.getColumnIndex(Data.RAW_CONTACT_ID)); 181 if (rawContactId != currentRawContactId) { 182 currentRawContactId = rawContactId; 183 rawContact = RawContact.fromResultSet(resultSet); 184 contactBuilder.rowContacts.push(rawContact); 185 } 186 if (rawContact != undefined) { 187 rawContact.dataItems.push(DataItem.fromResultSet(resultSet)); 188 } 189 } while (resultSet.goToNextRow()); 190 resultSet.close(); 191 callback(contactBuilder.buildContact()); 192 }).catch(error => { 193 HiLog.e(TAG, 'findByQuickSearchKey error:%s' + JSON.stringify(error.message)); 194 callback(); 195 }); 196 }).catch(error => { 197 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 198 callback(); 199 }); 200 } 201 202 findAllWithBookIndex() { 203 return new ContactList({}); 204 } 205 206 search(queryStr: string) { 207 return new ContactList({}); 208 } 209 210 findByPhoneIsNotNull(callback) { 211 this.getAllContactNumbers((contactNumberMap) => { 212 this.getDataAbilityHelper().then((dataAbilityHelper) => { 213 let conditionArgs = new dataSharePredicates.DataSharePredicates(); 214 conditionArgs.equalTo(RawContacts.IS_DELETED, '0') 215 .and() 216 .equalTo(Contacts.HAS_PHONE_NUMBER, '1') 217 .orderByAsc(RawContacts.SORT_FIRST_LETTER); 218 dataAbilityHelper.query(Contacts.CONTACT_URI, conditionArgs, ContactListItem.COLUMNS) 219 .then(resultSet => { 220 let rst: ContactListItem[] = []; 221 if (resultSet.rowCount === 0) { 222 resultSet.close(); 223 callback(rst); 224 } else { 225 resultSet.goToFirstRow(); 226 do { 227 let id = resultSet.getLong(resultSet.getColumnIndex(Contacts.ID)); 228 if (!contactNumberMap.has(id)) { 229 HiLog.w(TAG, 'findAll: contact id is invalid or contact has no phone number.'); 230 continue; 231 } 232 let contactListItem = new ContactListItem(resultSet); 233 contactListItem.phoneNumbers = contactNumberMap.get(id); 234 rst.push(contactListItem); 235 } while (resultSet.goToNextRow()); 236 resultSet.close(); 237 callback(rst); 238 } 239 }) 240 .catch(error => { 241 HiLog.w(TAG, 'findAll error:%s' + JSON.stringify(error.message)); 242 callback(); 243 }); 244 }).catch(error => { 245 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 246 callback(); 247 }); 248 }); 249 } 250 251 /** 252 * 查询所有联系人手机号 253 */ 254 private getAllContactNumbers(callback) { 255 this.getDataAbilityHelper().then((dataAbilityHelper) => { 256 let resultColumns = [RawContacts.CONTACT_ID, Data.DETAIL_INFO, Data.EXTEND7, Data.CUSTOM_DATA]; 257 let conditionArgs = new dataSharePredicates.DataSharePredicates(); 258 conditionArgs.equalTo(Data.TYPE_ID, DataItemType.PHONE).orderByAsc(RawContacts.CONTACT_ID); 259 dataAbilityHelper.query(Data.CONTENT_URI, conditionArgs, resultColumns).then(resultSet => { 260 // 用于存储联系人及其电话号码的对应关系 261 let contactNumberMap = new Map(); 262 if (resultSet == undefined || !resultSet.goToFirstRow()) { 263 HiLog.w(TAG, 'getAllContactNumbers not found.'); 264 callback(contactNumberMap); 265 return; 266 } 267 let oldContact = resultSet.getLong(resultSet.getColumnIndex(RawContacts.CONTACT_ID)); 268 let oldPhoneNumber = ""; 269 let numberList = []; 270 do { 271 let newContact = resultSet.getLong(resultSet.getColumnIndex(RawContacts.CONTACT_ID)); 272 let phoneNumberObj = { 273 'phoneNumber': resultSet.getString(resultSet.getColumnIndex(Data.DETAIL_INFO)), 274 'labelId': resultSet.getString(resultSet.getColumnIndex(Data.EXTEND7)), 275 'numType': resultSet.getString(resultSet.getColumnIndex(Data.CUSTOM_DATA)) 276 }; 277 // 如果是同一联系人则把手机号放到同一个list中 278 if (oldContact === newContact) { 279 if (oldPhoneNumber != phoneNumberObj.phoneNumber) { 280 numberList.push(phoneNumberObj); 281 } 282 } else { 283 // 联系人变化时,存储联系人与手机号码列表的对应关系 284 contactNumberMap.set(oldContact, numberList); 285 oldContact = newContact; 286 // 将最新的号码数据存储到新的numberList 287 numberList = [phoneNumberObj]; 288 } 289 oldPhoneNumber = phoneNumberObj.phoneNumber; 290 } while (resultSet.goToNextRow()); 291 contactNumberMap.set(oldContact, numberList); 292 resultSet.close(); 293 callback(contactNumberMap); 294 }); 295 }).catch(error => { 296 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 297 callback(new Map()); 298 }); 299 } 300 301 findByMailIsNotNull() { 302 return new ContactList({}); 303 } 304 305 deleteById(id: number, callback) { 306 if (id < 0) { 307 callback(); 308 return; 309 } 310 this.getDataAbilityHelper().then((dataAbilityHelper) => { 311 let condition = new dataSharePredicates.DataSharePredicates(); 312 condition.equalTo(Contacts.ID, id); 313 dataAbilityHelper.delete(Contacts.CONTACT_URI, condition).then(data => { 314 callback(data); 315 }).catch(error => { 316 HiLog.w(TAG, 'deleteById error:%s' + JSON.stringify(error.message)); 317 callback(); 318 }); 319 }).catch(error => { 320 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 321 callback(); 322 }); 323 } 324 325 deleteByIdIn(ids: number[]) { 326 return false; 327 } 328 329 notifyChange() { 330 this.getDataAbilityHelper().then((dataAbilityHelper) => { 331 if (dataAbilityHelper) { 332 dataAbilityHelper.notifyChange(Data.CONTENT_URI).then(() => { 333 HiLog.i(TAG, "notifyChange success") 334 }).catch(error => { 335 HiLog.w(TAG, 'notifyChange error:%s' + JSON.stringify(error.message)); 336 }); 337 } 338 }).catch(error => { 339 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 340 }); 341 } 342 343 observers:Array<()=>void> = []; 344 345 callback = ()=>{ 346 HiLog.d(TAG, 'Contacts changed: Notifying observers...'); 347 for (const observer of this.observers) { 348 observer(); 349 } 350 } 351 352 registerDataChangeObserver(observer:()=>void) { 353 if (!observer) { 354 HiLog.i(TAG, `registerDataChangeObserver: observer is null.`); 355 return; 356 } 357 if (this.observers.length == 0) { 358 this.getDataAbilityHelper().then((dataAbilityHelper) => { 359 if (dataAbilityHelper) { 360 dataAbilityHelper.on("dataChange", Data.CONTENT_URI, this.callback); 361 } 362 }).catch(error => { 363 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 364 }); 365 } 366 const isExist = this.observers.includes(observer); 367 if (isExist) { 368 return HiLog.i(TAG, 'registerDataChangeObserver: Observer has been attached already.'); 369 } 370 HiLog.i(TAG, 'registerDataChangeObserver: Attached an observer.'); 371 this.observers.push(observer); 372 } 373 374 unRegisterDataChangeObserver(observer:()=>void) { 375 const observerIndex = this.observers.indexOf(observer); 376 if (observerIndex === -1) { 377 HiLog.i(TAG, 'unRegisterDataChangeObserver: Nonexistent observer.'); 378 return 379 } 380 this.observers.splice(observerIndex, 1); 381 HiLog.i(TAG, 'unRegisterDataChangeObserver: Detached an observer.'); 382 if (this.observers.length == 0) { 383 this.getDataAbilityHelper().then((dataAbilityHelper) => { 384 if (dataAbilityHelper) { 385 dataAbilityHelper.off("dataChange", Data.CONTENT_URI, this.callback); 386 } 387 }).catch(error => { 388 HiLog.w(TAG, 'error:%s' + JSON.stringify(error.message)); 389 }); 390 } 391 } 392 393 queryContactDataByNumber(numberList, callback) { 394 this.getDataAbilityHelper().then((dataAbilityHelper) => { 395 if (!dataAbilityHelper) { 396 HiLog.e(TAG, "queryContactDataByNumber, dataAbilityHelper is null"); 397 callback([]); 398 return; 399 } 400 let resultColumns = [ 401 "detail_info", 402 "display_name", 403 ]; 404 let condition = new dataSharePredicates.DataSharePredicates(); 405 condition.in("detail_info", numberList); 406 condition.and(); 407 condition.equalTo("type_id", "5"); 408 condition.and(); 409 condition.equalTo("is_deleted", "0"); 410 dataAbilityHelper.query(Data.CONTENT_URI, condition, resultColumns).then(resultSet => { 411 callback(this.dealContactResultSet(resultSet)); 412 }).catch(error => { 413 HiLog.e(TAG, "queryContactDataByNumber query, error: " + JSON.stringify(error.message)); 414 }) 415 }).catch(error => { 416 HiLog.e(TAG, "queryContactDataByNumber, error: " + JSON.stringify(error.message)); 417 callback([]); 418 }); 419 } 420 421 dealContactResultSet(resultSet) { 422 let contacts = []; 423 while (resultSet?.goToNextRow()) { 424 let contact: {[key: string]: any} = {}; 425 contact.detailInfo = resultSet.getString(0); 426 contact.displayName = resultSet.getString(1); 427 contacts.push(contact); 428 } 429 return contacts; 430 } 431}