Contacts
Contact features provide functions for managing contact information, such as address books, groups, persons, and phone logs.
This feature is supported in mobile applications only.
A contact object is always associated with a specific address book. A person object is an aggregation of one or more contacts associated with the same person.
The following figure illustrates the contact structure. There are 3 instances of the same contact in different address books. Person1 is an aggregation of those instances (Contact1, Contact2, and Contact3).
Figure: Contact structure
The Contact Service supports vCards.
The main features of the Contacts API include:
- Contact management
- Manage individual contact records, such as name, phone number, email, address, job, instant messenger, and company with the help of contact data-views.
- Insert contacts to and remove them from the contacts database.
- Use filters and queries to search and organize contacts.
- Accounts
- Handle accounts using an account ID created with the Account Manager API.
- Have the related account ID of 0 because local device address book has no account.
- Create only one address book each.
- Address books
- Determine where contacts and groups belong.
- Create address books using the local device (with no account), service providers (such as Samsung account), or applications (such as ChatON or Joyn).
- Groups
- Combine contacts on the same address book.
- Set up many-to-many relationships between groups and contacts.
- Lists
- Persons
- Set up a virtual contact to be shown and managed when more than 1 contact from different sources designate the same individual.
- My profile
- Manage My profile. My profile has similar properties as the contact information, but no properties, such as group relation, ringtone, and message alert.
- Can have a single entry in each address book.
- Activities
- Store social activities.
- Speed dials
- Provide a shortcut dialing number key information.
- Phone logs
- Store call or message logs.
The following figure illustrates the different entities in the Contact Service, and their relationships.
Figure: Contact entities
Batch Operations
Bulk APIs provide insert, update, and delete operations for multiple records simultaneously. There is no limit on the record count for a bulk API, but it causes a process to hang while the API is operated. Bulk APIs guarantee atomicity. That is, the API operations are performed either for all, or nothing.
int contacts_db_insert_records(contacts_list_h record_list, int **ids, unsigned int *count); int contacts_db_update_records(contacts_list_h record_list); int contacts_db_delete_records(const char* view_uri, int record_id_array[], int count); int contacts_db_replace_records(contacts_list_h list, int record_id_array[], unsigned int count);
The following code example inserts 2 contact records using a bulk API:
contacts_record_h contact1; contacts_record_create(_contacts_contact.uri, &contact1); // Fill contact record contacts_record_h contact2; contacts_record_create(_contacts_contact._uri, &contact2); // Fill contact record contacts_list_h list = NULL; contacts_list_create(&list); contacts_list_add(list, contact1); contacts_list_add(list, contact2); int **ids = NULL; unsigned int count = 0; // Insert contact records using bulk APIs contacts_db_insert_records(list, &ids, &count); // Use IDs contacts_list_destroy(list, true); free(ids);
Database Change Notifications
To detect the contact database changes, use the Database API functions. Register a callback with the contacts_db_add_changed_cb() function. To unregister the callback and ignore database changes, use the contacts_db_remove_changed_cb() function.
Clients wait contacts change notifications on the client side. If the contact is changed by another module, the server publishes a notification. The notification module broadcasts to the subscribed modules and a user callback function is called with the user data. The following example registers person changes notification callback:
static void __person_changed_ cb(const char *view_uri, void *user_data) { // Jobs for the callback function } // Add a change notification callback contacts_db_add_changed_cb(_contacts_person._uri, __person_changed_cb, NULL);
Filters and Queries
Queries are used to retrieve data which satisfies a given criteria, like an integer property being greater than a given value, or a string property containing a given substring. Query needs a filter which can set the condition for search. The Contact Service provides query APIs for sorting set projections and removing duplicated results.
Filters
The Filter API provides the set of definitions and interfaces that enable application developers to make filters to set queries.
When creating a filter, specify the filter type you want to create using the _uri property. A filter handle must be destroyed after use.
int contacts_filter_create(const char* view_uri, contacts_filter_h* filter); int contacts_filter_destroy(contacts_filter_h filter);
The following example sets the filter condition to contain a given substring:
contacts_filter_add_str(filter, _contacts_person.display_name, CONTACTS_MATCH_CONTAINS, "1234");
The following example sets the filter condition to property value is true:
contacts_filter_add_bool(filter, _contacts_person.is_favorite, true);
Conditions can be added to a filter. Join the conditions by using the logical operators AND and OR.
The following example creates a composite filter with the OR operator.
contacts_filter_add_str(filter1, _contacts_person.display_name, CONTACTS_MATCH_CONTAINS, "1234"); contacts_filter_add_operator(filter1, CONTACTS_FILTER_OPERATOR_OR); contacts_filter_add_str(filter1, _contacts_person.display_name, CONTACTS_MATCH_CONTAINS, "5678");
Additionally, filters can join each other by using the logical operators AND and OR.
The following example creates a joined filter with the AND operator.
contacts_filter_add_str(filter1, _contacts_person.display_name, CONTACTS_MATCH_CONTAINS, "1234"); contacts_filter_add_operator(filter1, CONTACTS_FILTER_OPERATOR_OR); contacts_filter_add_str(filter1, _contacts_person.display_name, CONTACTS_MATCH_CONTAINS, "5678"); contacts_filter_add_bool(filter2, _contacts_person.is_favorite, true); contacts_filter_add_operator(filter1, CONTACTS_FILTER_OPERATOR_AND); contacts_filter_add_filter(filter1, filter2);
The operator precedence in filters is determined by the order in which the conditions and filters are added. The following table shows the results, if the following sequences are added.
Filters | Result |
---|---|
Condition C1 OR Condition C2 AND Condition C3 |
(C1 OR C2) AND C3 |
Filter F1: Condition C1 OR Condition C2 Filter F2: Condition C3 OR Condition C4 Filter F3: Condition C5 AND F1 AND F2 |
(C5 AND F1) AND F2, which is (C5 AND (C1 OR C2)) AND (C3 OR C4) |
The following example creates a filter which accepts addresses with their contact's ID equal to a given ID (integer filter), or their country property equal to "Korea" (string filter). Create a query and add the filter to it, results are received in a list.
contacts_filter_h filter = NULL; contacts_list_h list = NULL; contacts_query_h query = NULL; contacts_filter_create(_contacts_address._uri, &filter); contacts_filter_add_int(filter, _contacts_address.contact_id, CONTACTS_MATCH_EQUAL, id); contacts_filter_add_operator(filter, CONTACTS_FILTER_OPERATOR_OR); contacts_filter_add_str(filter, _contacts_address.country, CONTACTS_MATCH_EXACTLY, "Korea"); contacts_query_create(_contacts_address._uri, &query); contacts_query_set_filter(query, filter); contacts_db_get_records_with_query(query, 0, 0, &list); contacts_filter_destroy(filter); contacts_query_destroy(query); // Use the list contacts_list_destroy(list, true);
Sorting
Sort query results list by property ID:
int contacts_query_set_sort(contacts_query_h query, unsigned int property_id, bool is_ascending);
Sort query results by person ID:
contacts_filter_add_str(filter, _contacts_person.display_name, CONTACTS_MATCH_CONTAINS, "Joe"); contacts_query_set_filter(query, filter); contacts_query_set_sort(query, _contacts_person.id, true); contacts_db_get_records_with_query(query, 0, 0, &list); contacts_query_destroy(query); contacts_filter_destroy(filter); contacts_list_destroy(person_list, true);
Projection
Projection allows you to query data for specific properties of a record, at lower latency and cost than retrieving all properties:
int contacts_query_set_projection(contacts_query_h query, unsigned int property_ids[], int count)
The following example creates a filter which gets only the person ID, display name, and image thumbnail path from the person record which "test" (string filter) as vibration path. Create a query and add the filter to it, results are received in a list.
contacts_filter_add_str(filter, _contacts_person.vibration, CONTACTS_MATCH_CONTAINS, "test"); contacts_query_set_filter(query, filter); // Set projections unsigned int person_projection[] = { _contacts_person.id, _contacts_person.display_name, _contacts_person.image_thumbnail_path, }; contacts_query_set_projection(query, person_projection, sizeof(person_projection)/sizeof(int)); contacts_db_get_records_with_query(query, 0, 0, &person_list); // Use the list contacts_query_destroy(query); contacts_filter_destroy(filter); contacts_list_destroy(person_list, true);
Distinct
If you query a read-only view with a set projection, the result list can contain duplicates. Remove duplicates using contacts_query_set_distinct.
int contacts_query_set_distinct(contacts_query_h query, bool set)
The following example removes duplicates:
unsigned int projection[] = { _contacts_person_number.person_id, _contacts_person_number.display_name, }; contacts_filter_create(_contacts_person_number._uri, &filter); contacts_filter_add_bool(filter, _contacts_person_number.has_phonenumber, true); contacts_query_create(_contacts_person_number._uri, &query); contacts_query_set_projection(query, projection, sizeof(projection)/sizeof(int)); contacts_query_set_filter(query, filter); // Set distinct (remove duplicates) contacts_query_set_distinct(query, true); contacts_db_get_records_with_query(query, 0, 0, &list); // Use the list contacts_list_destroy(list, true); contacts_query_destroy(query); contacts_filter_destroy(filter);
Lists
The Contact Service provides functions which can handle lists of records with the same type. The list concept is based on a standard doubly-linked list.
To operate a list, you must obtain its handle. The handle is provided during the list creation. Destroy the list handle after use:
int contacts_list_create(contacts_list_h* contacts_list); int contacts_list_destroy(contacts_list_h contacts_list, bool delete_child);
If the delete_child parameter is true, child resources are destroyed automatically.
The following example creates a list handle:
// Get a list handle with a query contacts_list_h list = NULL; contacts_list_create(&list); // Use the list contacts_list_destroy(list, true);
The following example gets the person list handle from the database:
// Get a list handle with a query contacts_list_h list = NULL; contacts_db_get_all_records(_contacts_person._uri, 0, 0, &list); // Use the list contacts_list_destroy(list, true);
Cursor
The list can be traversed by using a cursor:
int contacts_list_first(contacts_list_h contacts_list); int contacts_list_last(contacts_list_h contacts_list); int contacts_list_next(contacts_list_h contacts_list); int contacts_list_prev(contacts_list_h contacts_list);
Get a record of the current cursor:
int contacts_list_get_current_record_p(contacts_list_h contacts_list, contacts_record_h* record);
The following example creates a loop list:
contacts_list_h list = NULL; contacts_record_h record = NULL; contacts_db_get_all_records(_contacts_person._uri, 0, 0, &list); do { contacts_list_get_current_record_p(list, &record); if (NULL == record) break; char *name = NULL; contacts_record_get_str_p(record, _contacts_person.display_name, &name); dlog_print(DLOG_DEBUG, LOG_TAG, "name=%s", name); } while (CONTACTS_ERROR_NONE == contacts_list_next(list)); contacts_list_destroy(list, true); // Destroy child records automatically
Adding and Removing
The Contact Service provides functions for adding and removing child records on a list.
int contacts_list_add(contacts_list_h contacts_list, contacts_record_h record); int contacts_list_remove(contacts_list_h contacts_list, contacts_record_h record);
The following example adds records to the list:
contacts_record_h group1 = NULL; contacts_record_create(_contacts_group._uri, &group1); contacts_record_set_str(group1, _contacts_group.name, "group test1"); contacts_record_h group2 = NULL; contacts_record_create(_contacts_group._uri, &group2); contacts_record_set_str(group2, _contacts_group.name, "group test2"); contacts_list_h list = NULL; contacts_list_create(&list); // Add records to the list contacts_list_add(list, group1); contacts_list_add(list, group2); contacts_db_insert_records(list, NULL, NULL); contacts_list_destroy(list, true);
Persons and Contacts
Persons are virtual contacts that keep merged information of contacts linked together. A person is created automatically when a contact record is inserted in the Contacts database. A person record cannot be created directly. Every contact must be linked to at least 1 person.
The following figure illustrates the process of creating a person record.
Figure: Creating a person
A person record can be linked to another person. Linking is possible even though the contacts have different address books.
The following figure illustrates the process of linking a person.
Figure: Linking a person
A contact record can also be separated from the person record. As a result of unlinking the contact record, a new person is created.
The following figure illustrates the process of unlinking a contact.
Figure: Unlinking a contact
Records
An important concept in the Contacts APIs is a record. Although a record represents an actual record in the internal database, you can consider it a piece of information, like an address, phone number, or group of contacts. A record can be a complex set of data, containing other data. For example, an address record contains country, region, and street information.
The contained data can also be a reference to another record. For example, a contact record contains the address property, which is a reference to an address record. An address record belongs to a contact record - its contact_id property is set to the identifier of the corresponding contact. In this case, the address is the child record of the contact and the contact is the parent record.
Effectively, a record can be a node in a tree or graph of relations between records.
URI
A record type is identified by a structure called the view, which contains identifiers of its properties. Every view has a special field - _uri - that uniquely identifies the view. In many cases, you must provide the _uri value to indicate what type of record you want to create or operate on.
The following APIs need a _uri postfix:
- int contacts_record_create(const char* view_uri, ...)
- int contacts_filter_create(const char* view_uri, ...)
- int contacts_query_create(const char* view_uri, ...)
- int contacts_db_get_record(const char* view_uri, ...)
- int contacts_db_delete_record(const char* view_uri, ...)
- int contacts_db_get_all_records(const char* view_uri, ...)
- int contacts_db_delete_records(const char* view_uri, ...)
- int contacts_db_add_changed_cb(const char* view_uri, ...)
- int contacts_db_remove_changed_cb(const char* view_uri, ...)
- int contacts_db_get_changes_by_version(const char* view_uri, ...)
- int contacts_db_search_records(const char* view_uri, ...)
- int contacts_db_search_records_with_range(const char* view_uri, ...)
- int contacts_db_get_count(const char* view_uri, ...)
Record Handle
To use a record, you must obtain its handle. There are many methods to obtain the handle. For example, you can create a new record or refer to the child records of a record.
When creating a record, you must specify what type of record you want to create by using the URI property. The following code examples creates a contact record and obtains its handle:
contacts_record_h contact = NULL; contacts_record_create(_contacts_contact._uri, &contact);
The following example shows how to get a contact record with ID:
contacts_record_h contact = NULL; contacts_db_get_record(_contacts_contact._uri, id, &contact);
Basic Types
Records contain properties of basic types: integer, string, boolean, long integer, lli (long long int), and double.
The following table lists the setter and getter functions for each type.
Property | Setter | Getter |
---|---|---|
string | contacts_record_set_str | contacts_record_get_str |
integer | contacts_record_set_int | contacts_record_get_int |
boolean | contacts_record_set_bool | contacts_record_get_bool |
long long integer | contacts_record_set_lli | contacts_record_get_lli |
double | contacts_record_set_double | contacts_record_get_double |
The above functions also require specifying which property to get and set. Every getter and setter function needs a record and property ID. Create a property ID by combining the data-view and property name. For example, the property ID of a contact display_name property: _contacts_contact.display_name)
The following example sets the ringtone_path property of a contact record.
char *resource_path = app_get_resource_path(); char ringtone_path[1024]; snprintf(ringtone_path, sizeof(ringtone_path), "%s/ringtone.mp3", resource_path); free(resource_path); contacts_record_set_str(contact, _contacts_contact.ringtone_path, ringtone_path);
Note |
---|
The string getter functions have the _p postfix. It means that the returned value should not be freed by the application, as it is a pointer to data in an existing record. |
The following example shows that there are 2 ways of getting string property:
contacts_record_get_str(record, _contacts_person.display_name, &display_name); contacts_record_get_str_p(record, _contacts_person.display_name, &display_name);
In the first case, the returned string should be freed by the application. In second one, the display_name value is freed automatically when destroying the record handle.
Children
A record can have properties of the record type - called child records. A record can contain several records of a given type. For example, a contact record (parent) can contain many address records (children).
Figure: Children
The following code example inserts an address record into a contact record. Note that it is not necessary to insert or destroy all records - just the parent record needs to be inserted into the database to store all the information, and when the parent record is destroyed, the child records are also destroyed automatically.
The following example adds a child record:
contacts_record_h address = NULL; contacts_record_h image = NULL; int contact_id = 0; // Image and address record can be child records of contact record contacts_record_create(_contacts_contact._uri, &contact); contacts_record_create(_contacts_image._uri, &image); char *resource_path = app_get_resource_path(); char caller_id_path[1024]; snprintf(caller_id_path, sizeof(caller_id_path), "%s/caller_id.jpg", resource_path); free(resource_path); contacts_record_set_str(image, _contacts_image.path, caller_id_path); contacts_record_add_child_record(contact, _contacts_contact.image, image); contacts_record_create(_contacts_address._uri, &address); contacts_record_set_str(address, _contacts_address.country, "Korea"); contacts_record_add_child_record(contact, _contacts_contact.address, address); // Insert contact to the database contacts_db_insert_record(contact, &contact_id); contacts_record_destroy(contact, true);
Note |
---|
For an application to insert private images in contacts, the following conditions apply:
|
Record ID Property
An ID is a unique number for identifying records. Therefore, if you know the ID of a record, you can directly handle the record. The ID is read-only property, which is available after the record has been inserted into the database. The following example gets a contact record with an ID:
contacts_record_h contact = NULL; contacts_record_create(_contacts_contact._uri, &contact); contacts_record_h name = NULL; contacts_record_create(_contacts_name._uri, &name); contacts_record_set_str(name, _contacts_name.first, "first name"); contacts_record_add_child_record(contact, _contacts_contact.name, name); int contact_id = 0; contacts_db_insert_record(contact, &contact_id); contacts_record_destroy(contact, true); // Contact is no longer usable contacts_db_get_record(_contacts_contact._uri, contact_id, &contact); // Contact is now a handle to the same record as before char *display_name = NULL; contacts_record_get_str(contact, _contacts_contact.display_name, &display_name); contacts_record_destroy(contact, true); // Contact is no longer usable
Identifiers can be used to establish a relationship between 2 records. The following code example sets an address record's contact_id property to the ID of the contact. The contact_id relates between the address record and the contact which is identified by the contact_id. After the ID is set, the address becomes one of the addresses connected to the contact. The address is now the contact's child record, and the contact is the parent record. The following example adds an address record to a contact_id:
int contact_id = ... // Acquire the ID of the created contact int address_id = 0; contacts_record_create(_contacts_address._uri, &address); contacts_record_set_int(address, _contacts_address.contact_id, contact_id); // Set other address properties contacts_db_insert_record(address, &address_id);
With a record handle, you can access all records of a specific type related to the given record.
The following code example changes a country of addresses which are child records of a contact. Each address can be traversed by using the contacts_record_get_child_record_at_p() function. It is possible to apply the changes by updating the contact which is the parent record:
int contact_id = ... // Acquire ID of the created contact unsigned int address_num = 0; int i = 0; contacts_db_get_record(_contacts_contact._uri, contact_id, &contact); contacts_record_get_child_record_count(contact, _contacts_contact.address, &address_num); for (i = 0; i < address_num; i++) { contacts_record_h address = NULL; contacts_record_get_child_record_at_p(contact, _contacts_contact.address, i, &address); contacts_record_set_str(address, _contacts_address.country, "Korea"); } contacts_db_update_record(contact); contacts_record_destroy(contact, true);
Views and Properties
Views are provided to access and handle entities. According to data-view declarations, generic access functions, such as contacts_db_insert_record() and contacts_record_get_int, can be used to access contact views. A data-view is almost the same as a database "VIEW", which limits access and guarantees performance. A "record" represents a single row of the data-views.
A data-view is a structure, which has property elements. For example, the _contacts_contact view describes the properties of the contact record. Its properties include, for example, name, company, and nickname of the contact. The property elements have their data types and names.
The record types that have *_id as their property hold identifiers of other records. For example, the name, number, and email views hold the ID of their corresponding contacts in the contact_id property as children of the corresponding contact records.
The property type record means that the parent record can have child records. For example, a contact record has name, number, and email properties, which means that records of those types can be children of the contact type records. The following figure illustrates macros in a contacts_view.h header file.
Figure: Properties
For more information, see the View/Property API.
vCards
The Contact Service provides functions for parsing and making vCards. The vCard APIs are based on the vCard v3.0 specification.
Parsing vCards
There are 2 ways for parsing vCards. The following example shows parsing vCard from stream and inserting it to the database.
// Make a contact record list from the vCard stream contacts_list_h list = NULL; contacts_vcard_parse_to_contacts(vcard_stream, &list); // Use the contact record list contacts_list_destroy(list, true);
The following example shows parsing vCard from a file and inserting it to database:
// Get a record handle of the _contacts_contact view static bool __vcard_parse_cb(contacts_record_h record, void *user_data) { int id = 0; contacts_db_insert_record(record, &id); // Return false to break out of the loop // Return true to continue with the next iteration of the loop return true; } // Parse the vCard from a file char *resource_path = app_get_resource_path(); char vcard_path[1024]; snprintf(vcard_path, sizeof(vcard_path), "%s/vcard.vcf", resource_path); free(resource_path); contacts_vcard_parse_to_contact_foreach(vcard_path, __vcard_parse_cb, NULL);
Making a vCard Stream
You can make a vCard stream from a contact, person, or my profile record. The following code example makes a vCard stream using a contact record:
contacts_record_h contact; char *vcard_stream = NULL; contacts_db_get_record(_contacts_contact._uri, contact_id, &contact); contacts_vcard_make_from_contact(contact, &vcard_stream); // Use the vcard stream free(vcard_stream); contacts_record_destroy(contact, true);