org.xwalk.runtime.extension.api.contacts.ContactSaver.java Source code

Java tutorial

Introduction

Here is the source code for org.xwalk.runtime.extension.api.contacts.ContactSaver.java

Source

// Copyright (c) 2013 Intel Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.xwalk.runtime.extension.api.contacts;

import android.content.ContentProviderOperation;
import android.content.ContentProviderOperation.Builder;
import android.content.ContentResolver;
import android.content.OperationApplicationException;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
import android.util.Log;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import org.xwalk.runtime.extension.api.contacts.ContactConstants.ContactMap;

/**
 * This class saves contacts according to a given JSONString.
 */
public class ContactSaver {
    private ContactUtils mUtils;
    private static final String TAG = "ContactSaver";

    private JSONObject mContact;
    private ContactJson mJson;
    private String mId;
    private boolean mIsUpdate;
    private ArrayList<ContentProviderOperation> mOps;

    public ContactSaver(ContentResolver resolver) {
        mUtils = new ContactUtils(resolver);
    }

    // Update a contact
    private Builder newUpdateBuilder(String mimeType) {
        Builder builder = ContentProviderOperation.newUpdate(Data.CONTENT_URI);
        builder.withSelection(Data.CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", new String[] { mId, mimeType });
        return builder;
    }

    // Add a new contact
    private Builder newInsertBuilder(String mimeType) {
        Builder builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
        builder.withValueBackReference(Data.RAW_CONTACT_ID, 0);
        builder.withValue(Data.MIMETYPE, mimeType);
        return builder;
    }

    // Add a new field to an existing contact
    private Builder newInsertFieldBuilder(String mimeType) {
        String rawId = mUtils.getRawId(mId);
        if (rawId == null) {
            Log.e(TAG, "Failed to create builder to insert field of " + mId);
            return null;
        }
        Builder builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
        builder.withValue(Data.RAW_CONTACT_ID, mUtils.getRawId(mId));
        builder.withValue(Data.MIMETYPE, mimeType);
        return builder;
    }

    // Add a new contact or add a new field to an existing contact
    private Builder newInsertContactOrFieldBuilder(String mimeType) {
        return mIsUpdate ? newInsertFieldBuilder(mimeType) : newInsertBuilder(mimeType);
    }

    // Add a new contact or update a contact
    private Builder newBuilder(String mimeType) {
        return mIsUpdate ? newUpdateBuilder(mimeType) : newInsertBuilder(mimeType);
    }

    // Build by a data array with types
    private void buildByArray(ContactMap contactMap) {
        if (!mContact.has(contactMap.mName)) {
            return;
        }

        // When updating multiple records of one MIMEType,
        // we need to flush the old records and then insert new ones later.
        //
        // For example, it is possible that a contact has several phone numbers,
        // in data table it will be like this:
        // CONTACT_ID  MIMETYPE  TYPE  DATA1
        // ------------------------------------------
        //        374  Phone_v2  Work  +4412345678
        //        374  Phone_v2  Work  +4402778877
        // In this case if we update by SQL selection clause directly,
        // will get two same records of last update value.
        //
        if (mIsUpdate) {
            mUtils.cleanByMimeType(mId, contactMap.mMimeType);
        }
        try {
            final JSONArray fields = mContact.getJSONArray(contactMap.mName);
            for (int i = 0; i < fields.length(); ++i) {
                ContactJson json = new ContactJson(fields.getJSONObject(i));
                List<String> typeList = json.getStringArray("types");
                if (typeList != null && !typeList.isEmpty()) {
                    // Currently we can't store multiple types in Android
                    final String type = typeList.get(0);
                    final Integer iType = contactMap.mTypeValueMap.get(type);

                    Builder builder = newInsertContactOrFieldBuilder(contactMap.mMimeType);
                    if (builder == null)
                        return;

                    if (json.getBoolean("preferred")) {
                        builder.withValue(contactMap.mTypeMap.get("isPrimary"), 1);
                        builder.withValue(contactMap.mTypeMap.get("isSuperPrimary"), 1);
                    }
                    if (iType != null) {
                        builder.withValue(contactMap.mTypeMap.get("type"), iType);
                    }
                    for (Map.Entry<String, String> entry : contactMap.mDataMap.entrySet()) {
                        String value = json.getString(entry.getValue());
                        if (contactMap.mName.equals("impp")) {
                            int colonIdx = value.indexOf(':');
                            // An impp must indicate its protocol type by ':'
                            if (-1 == colonIdx)
                                continue;
                            String protocol = value.substring(0, colonIdx);
                            builder.withValue(Im.PROTOCOL, ContactConstants.imProtocolMap.get(protocol));
                            value = value.substring(colonIdx + 1);
                        }
                        builder.withValue(entry.getKey(), value);
                    }
                    mOps.add(builder.build());
                }
            }
        } catch (JSONException e) {
            Log.e(TAG, "Failed to parse json data of " + contactMap.mName + ": " + e.toString());
        }
    }

    // Build by a data array without types
    private void buildByArray(String mimeType, String data, List<String> dataEntries) {
        if (mIsUpdate) {
            mUtils.cleanByMimeType(mId, mimeType);
        }
        for (String entry : dataEntries) {
            Builder builder = newInsertContactOrFieldBuilder(mimeType);
            if (builder == null)
                return;
            builder.withValue(data, entry);
            mOps.add(builder.build());
        }
    }

    private void buildByArray(ContactMap contactMap, String data, List<String> dataEntries) {
        if (mContact.has(contactMap.mName)) {
            buildByArray(contactMap.mMimeType, data, dataEntries);
        }
    }

    private void buildByDate(String name, String mimeType, String data) {
        buildByDate(name, mimeType, data, null, 0);
    }

    private void buildByDate(String name, String mimeType, String data, String type, int dateType) {
        if (!mContact.has(name))
            return;

        final String fullDateString = mJson.getString(name);
        final String dateString = mUtils.dateTrim(fullDateString);
        Builder builder = newBuilder(mimeType);
        builder.withValue(data, dateString);
        if (type != null)
            builder.withValue(type, dateType);
        mOps.add(builder.build());
    }

    private void buildByEvent(String eventName, int eventType) {
        buildByDate(eventName, Event.CONTENT_ITEM_TYPE, Event.START_DATE, Event.TYPE, eventType);
    }

    private void buildByContactMapList() {
        for (ContactMap contactMap : ContactConstants.contactMapList) {
            if (contactMap.mTypeMap != null) { // Field that has type.
                buildByArray(contactMap);
            } else { // Field that contains no type.
                buildByArray(contactMap, contactMap.mDataMap.get("data"), mJson.getStringArray(contactMap.mName));
            }
        }
    }

    private void PutToContact(String id) {
        if (id == null)
            return;
        try {
            mContact.put("id", id);
        } catch (JSONException e) {
            Log.e(TAG, "Failed to put id " + id + " into contact" + e.toString());
        }
    }

    public JSONObject save(String saveString) {
        mOps = new ArrayList<ContentProviderOperation>();
        try {
            mContact = new JSONObject(saveString);
        } catch (JSONException e) {
            Log.e(TAG, "Failed to parse json data: " + e.toString());
            return new JSONObject();
        }

        mJson = new ContactJson(mContact);

        Builder builder = null;
        mId = mJson.getString("id");
        mIsUpdate = mUtils.hasID(mId);

        Set<String> oldRawIds = null;
        if (!mIsUpdate) { // Create a null record for inserting later
            oldRawIds = mUtils.getCurrentRawIds();
            mId = null;
            builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
            builder.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null);
            builder.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
            mOps.add(builder.build());
        }

        // W3C                  Android
        //-------------------------------------------------
        // displayName          StructuredName.display_name
        // honorificPrefixes    StructuredName.prefix
        // givenNames           StructuredName.given_name
        // additionalNames      StructuredName.middle_name
        // familyNames          StructuredName.family_name
        // honorificSuffixes    StructuredName.suffix
        // nicknames            Nickname.name
        if (mContact.has("name")) {
            final JSONObject name = mJson.getObject("name");
            final ContactJson nameJson = new ContactJson(name);
            builder = newBuilder(StructuredName.CONTENT_ITEM_TYPE);
            builder.withValue(StructuredName.DISPLAY_NAME, nameJson.getString("displayName"));
            //FIXME(hdq): should read all names
            builder.withValue(StructuredName.FAMILY_NAME, nameJson.getFirstValue("familyNames"));
            builder.withValue(StructuredName.GIVEN_NAME, nameJson.getFirstValue("givenNames"));
            builder.withValue(StructuredName.MIDDLE_NAME, nameJson.getFirstValue("additionalNames"));
            builder.withValue(StructuredName.PREFIX, nameJson.getFirstValue("honorificPrefixes"));
            builder.withValue(StructuredName.SUFFIX, nameJson.getFirstValue("honorificSuffixes"));
            mOps.add(builder.build());

            // Nickname belongs to another mimetype, so we need another builder for it.
            if (name.has("nicknames")) {
                builder = newBuilder(Nickname.CONTENT_ITEM_TYPE);
                builder.withValue(Nickname.NAME, nameJson.getFirstValue("nicknames"));
                mOps.add(builder.build());
            }
        }

        if (mContact.has("categories")) {
            List<String> groupIds = new ArrayList<String>();
            for (String groupTitle : mJson.getStringArray("categories")) {
                groupIds.add(mUtils.getEnsuredGroupId(groupTitle));
            }
            buildByArray(GroupMembership.CONTENT_ITEM_TYPE, GroupMembership.GROUP_ROW_ID, groupIds);
        }

        if (mContact.has("gender")) {
            final String gender = mJson.getString("gender");
            if (Arrays.asList("male", "female", "other", "none", "unknown").contains(gender)) {
                builder = newBuilder(ContactConstants.CUSTOM_MIMETYPE_GENDER);
                builder.withValue(Data.DATA1, gender);
                mOps.add(builder.build());
            }
        }

        buildByDate("lastUpdated", ContactConstants.CUSTOM_MIMETYPE_LASTUPDATED, Data.DATA1);
        buildByEvent("birthday", Event.TYPE_BIRTHDAY);
        buildByEvent("anniversary", Event.TYPE_ANNIVERSARY);

        buildByContactMapList();

        // Perform the operation batch
        try {
            mUtils.mResolver.applyBatch(ContactsContract.AUTHORITY, mOps);
        } catch (Exception e) {
            if (e instanceof RemoteException || e instanceof OperationApplicationException
                    || e instanceof SecurityException) {
                Log.e(TAG, "Failed to apply batch: " + e.toString());
                return new JSONObject();
            } else {
                throw new RuntimeException(e);
            }
        }

        // If it is a new contact, need to get and return its auto-generated id.
        if (!mIsUpdate) {
            Set<String> newRawIds = mUtils.getCurrentRawIds();
            if (newRawIds == null)
                return new JSONObject();
            newRawIds.removeAll(oldRawIds);
            if (newRawIds.size() != 1) {
                Log.e(TAG, "Something wrong after batch applied, " + "new raw ids are: " + newRawIds.toString());
                return mContact;
            }
            String id = mUtils.getId(newRawIds.iterator().next());
            PutToContact(id);
        }
        return mContact;
    }
}