com.polyvi.xface.extension.contact.XContactsExt.java Source code

Java tutorial

Introduction

Here is the source code for com.polyvi.xface.extension.contact.XContactsExt.java

Source

/*
 This file was modified from or inspired by Apache Cordova.
    
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements. See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership. The ASF licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License. You may obtain a copy of the License at
    
http://www.apache.org/licenses/LICENSE-2.0
    
 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 KIND, either express or implied. See the License for the
 specific language governing permissions and limitations
 under the License.
*/

package com.polyvi.xface.extension.contact;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

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

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;

import com.polyvi.xface.extension.XActivityResultListener;
import com.polyvi.xface.extension.XCallbackContext;
import com.polyvi.xface.extension.XExtension;
import com.polyvi.xface.extension.XExtensionResult;
import com.polyvi.xface.extension.XExtensionResult.Status;
import com.polyvi.xface.util.XConstant;
import com.polyvi.xface.util.XLog;
import com.polyvi.xface.util.XNotification;
import com.polyvi.xface.util.XStringUtils;

public class XContactsExt extends XExtension implements XActivityResultListener {

    private static final String CLASS_NAME = XContactsExt.class.getSimpleName();

    /** XContactsExt ??js??? */
    private static final String COMMAND_SEARCH = "search";
    private static final String COMMAND_SAVE = "save";
    private static final String COMMAND_REMOVE = "remove";
    private static final String COMMAND_CHOOSE = "chooseContact";
    private static final int PICK_CONTACT = 1;
    private static final String TAG_FIELDS = "fields";
    private static final String CALL_CHOOSE_ERROR = "error occurred while call chooseContact";

    // FIXME: ???
    // ?
    private static final int UNKNOWN_ERROR = 0;
    private static final int INVALID_ARGUMENT_ERROR = 1;
    private static final int TIMEOUT_ERROR = 2;
    private static final int PENDING_OPERATION_ERROR = 3;
    private static final int IO_ERROR = 4;
    private static final int NOT_SUPPORTED_ERROR = 5;
    private static final int PERMISSION_DENIED_ERROR = 20;

    // ?? 1 MB 
    private static final long MAX_PHOTO_SIZE = 1048576;

    private XContactAccessor mContactAccessor;
    private XCallbackContext mCallbackCtx;
    private JSONArray mFields;

    @Override
    public void sendAsyncResult(String result) {
    }

    @Override
    public boolean isAsync(String action) {
        return true;
    }

    @Override
    protected Object getNativeWorker() {
        return new XContactAccessorAPILevel5Impl(getContext());
    }

    /**
     * XExtensionResult.
     *
     * @param action
     *            ?action.
     * @param args
     *             Extension ? JSONArry.
     * @param callbackCtx
     *            nativejs
     * @return  status  message  XExtensionResult 
     */
    @Override
    public XExtensionResult exec(String action, JSONArray args, XCallbackContext callbackCtx) throws JSONException {
        XExtensionResult.Status status = XExtensionResult.Status.OK;

        //  Android ??? 2.x(API > 4) Android 
        if (android.os.Build.VERSION.RELEASE.startsWith("1.")) {
            return new XExtensionResult(XExtensionResult.Status.ERROR, XContactsExt.NOT_SUPPORTED_ERROR);
        }

        //  Android version ?? mContactAccessor??
        if (this.mContactAccessor == null) {
            this.mContactAccessor = (XContactAccessor) getNativeWorker();
        }

        if (action.equals(COMMAND_SEARCH)) {
            if (args.length() < 2) {
                return new XExtensionResult(Status.ERROR, XContactsExt.INVALID_ARGUMENT_ERROR);
            }
            JSONArray ret = search(args.getJSONArray(0), args.optJSONObject(1));
            return new XExtensionResult(status, ret);
        } else if (action.equals(COMMAND_SAVE)) {
            String id = saveContact(args.getJSONObject(0));
            if (id != null) {
                JSONObject ret = getContactById(id);
                if (ret != null) {
                    return new XExtensionResult(status, ret);
                }
            }
        } else if (action.equals(COMMAND_REMOVE)) {
            if (mContactAccessor.removeContact(args.getString(0))) {
                return new XExtensionResult(status, "");
            }
        } else if (action.equals(COMMAND_CHOOSE)) {
            mCallbackCtx = callbackCtx;
            mFields = getFields(args);
            startContactActivity();
            XExtensionResult r = new XExtensionResult(XExtensionResult.Status.NO_RESULT);
            return r;
        }
        return new XExtensionResult(Status.ERROR, XContactsExt.UNKNOWN_ERROR);
    }

    /**
     * ?JS?.
     *
     * @param fields
     *            ?????fields
     * @param options
     *            ???
     * @return
     */
    private JSONArray search(JSONArray fields, JSONObject options) {
        // 
        String searchTerm = "";
        int contactsMaxNum = Integer.MAX_VALUE; // ??
        boolean isMultipleContacts = true; // ?????

        if (options != null) {
            searchTerm = options.optString(XContactAccessor.FIND_OPTION_NAME_FILTER);
            if (XStringUtils.isEmptyString(searchTerm)) {
                searchTerm = "%";
            } else if (!"%".equals(searchTerm)) {
                searchTerm = "%" + searchTerm + "%";
            }

            try {
                isMultipleContacts = options.getBoolean(XContactAccessor.FIND_OPTION_NAME_MULTIPLE);
                if (!isMultipleContacts) {
                    contactsMaxNum = 1;
                }
            } catch (JSONException e) {
                // ? true???
            }
        } else {
            searchTerm = "%";
        }

        //  fields ? "*"
        boolean willMatchAllField = false;
        if (fields.length() == 1) {
            try {
                if ("*".equals(fields.getString(0))) {
                    willMatchAllField = true;
                }
            } catch (JSONException e) {
                // isWildCardSearch false???
            }
        }

        String accountType = options.optString(XContactAccessor.FIND_OPTION_NAME_ACCOUNT_TYPE);

        HashSet<String> logicContactFields = getContactFieldSet(fields);

        Cursor c = mContactAccessor.getMatchedContactsCursor(logicContactFields, searchTerm, willMatchAllField,
                accountType);
        // ?
        JSONArray contacts = populateContactArray(logicContactFields, c, contactsMaxNum);
        c.close();
        return contacts;
    }

    /**
     * JSONArray??HashSet?
     *
     * @param fields
     *            js???
     */
    protected HashSet<String> getContactFieldSet(JSONArray fields) {
        HashSet<String> requiredFieldsSet = new HashSet<String>();

        try {
            // fields ["*"]
            if (fields.length() == 1 && fields.getString(0).equals("*")) {
                // ?field name??
                requiredFieldsSet.addAll(XContactAccessor.LOGIC_CONTACT_FIELDS);
            } else {
                int len = fields.length();
                for (int i = 0; i < len; i++) {
                    //  JS ???? Impl 
                    requiredFieldsSet.add(fields.getString(i));
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
            XLog.e(CLASS_NAME, e.getMessage(), e);
        }
        return requiredFieldsSet;
    }

    /**
     * ????json.
     *
     * @param requiredField
     *            ????
     * @param c
     *            
     * @param contactsMaxNum
     *            ?
     * @return ? JSON 
     */

    private JSONArray populateContactArray(HashSet<String> requiredField, Cursor c, int contactsMaxNum) {
        String oldContactId = "";
        boolean newContact = true;

        JSONArray contacts = new JSONArray();
        JSONObject contact = new JSONObject();

        HashMap<String, Object> contactMap = new HashMap<String, Object>();

        // ??
        mContactAccessor.initQuantityOfFields(contactMap);

        if (c.getCount() > 0) {
            while (c.moveToNext() && (contacts.length() <= (contactsMaxNum - 1))) {
                String contactId = mContactAccessor.getContactId(c);
                String rawId = mContactAccessor.getRawId(c);
                try {
                    //  oldContactId
                    if (c.getPosition() == 0) {
                        oldContactId = contactId;
                    }

                    // contact ID?
                    // ?Contact object contacts
                    //  Contact object
                    if (!oldContactId.equals(contactId)) {
                        hashMapToJson(requiredField, contactMap, contact);
                        contacts.put(contact);

                        // 
                        contact = new JSONObject();
                        contactMap.clear();

                        // ??
                        mContactAccessor.initQuantityOfFields(contactMap);
                        newContact = true;
                    }

                    // ?  ID  display name
                    // ????
                    if (newContact) {
                        newContact = false;
                        contact.put(XContactAccessor.LOGIC_FIELD_ID, contactId);
                        contact.put(XContactAccessor.LOGIC_FIELD_RAWID, rawId);
                    }

                    mContactAccessor.cursorToHashMap(contactMap, c);
                } catch (JSONException e) {
                    XLog.e(CLASS_NAME, e.getMessage(), e);
                }

                oldContactId = contactId;
            }

            // Push?? contacts
            if (contacts.length() < contactsMaxNum) {
                hashMapToJson(requiredField, contactMap, contact);
                contacts.put(contact);
            }
        }
        return contacts;
    }

    /**
     * hash???json.
     *
     * @param requiredFields
     *            ????
     * @param contactMap
     *            ?? Map
     * @param contactJson
     *            ?? JSON ?
     */
    private void hashMapToJson(HashSet<String> requiredFields, HashMap<String, Object> contactMap,
            JSONObject contactJson) {
        try {
            // DisplayName?Nickname?Note?Birthday
            for (String logicField : XContactAccessor.LOGIC_CONTACT_SINGLE_VALUE_FIELDS) {
                if (requiredFields.contains(logicField)) {
                    Object fieldValue = contactMap.get(logicField);
                    if (fieldValue != null) {
                        contactJson.put(logicField, fieldValue);
                    }
                }
            }

            // Addresses?Organization?Phone?Email?IMs?URLs?Photos
            for (String logicParentField : XContactAccessor.LOGIC_CONTACT_MULTIPLE_VALUE_FIELDS) {
                if (requiredFields.contains(logicParentField)) {
                    String quantityField = mContactAccessor.generateQuantityFieldOf(logicParentField);
                    int quantityValue = (Integer) contactMap.get(quantityField);
                    if (quantityValue > 0) {
                        JSONArray jsonArray = new JSONArray();
                        for (int i = 0; i < quantityValue; i++) {
                            JSONObject childJson = new JSONObject();
                            List<String> subFields = mContactAccessor.getLogicContactSubFieldsOf(logicParentField);

                            for (String subField : subFields) {
                                String key = mContactAccessor.generateSubFieldKey(logicParentField, i, subField);
                                childJson.put(subField, contactMap.get(key));
                            }

                            jsonArray.put(childJson);
                        }
                        contactJson.put(logicParentField, jsonArray);
                    }
                }
            }

            String logicParentField = XContactAccessor.LOGIC_FIELD_NAME;
            // Name
            if (requiredFields.contains(logicParentField)) {
                JSONObject fieldJSON = new JSONObject();

                List<String> subFields = mContactAccessor.getLogicContactSubFieldsOf(logicParentField);

                for (String subField : subFields) {
                    String key = mContactAccessor.generateSubFieldKey(logicParentField, 0, subField);
                    fieldJSON.put(subField, contactMap.get(key));
                }

                String key = mContactAccessor.generateSubFieldKey(logicParentField, 0,
                        XContactAccessor.LOGIC_FIELD_NAME_FORMATTED);
                fieldJSON.put(XContactAccessor.LOGIC_FIELD_NAME_FORMATTED, contactMap.get(key));

                contactJson.put(XContactAccessor.LOGIC_FIELD_NAME, fieldJSON);
            }

        } catch (JSONException e) {
            e.printStackTrace();
            XLog.e(CLASS_NAME, e.getMessage(), e);
        }
    }

    /**
     *  property JSON .
     *
     * @param obj
     *            property JSON ?
     * @param property
     *            ?? property ??
     * @return  property 
     */
    private String getJSONString(JSONObject obj, String property) {
        String value = null;
        try {
            if (obj != null) {
                value = obj.getString(property);
                if (value.equals("null")) {
                    XLog.i(CLASS_NAME, property + " is 'null'");
                    value = null;
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
            XLog.d(CLASS_NAME, "Could not get = " + e.getMessage());
        }
        return value;
    }

    /**
     * ??. ? JSONObject ?? field  ? map ? impl 
     *
     * @param contact
     *            ?? JSON
     * @return ?? id null
     */
    private String saveContact(JSONObject contact) {
        HashMap<String, Object> propValue = new HashMap<String, Object>();

        String id = getJSONString(contact, XContactAccessor.LOGIC_FIELD_ID);
        propValue.put(XContactAccessor.LOGIC_FIELD_ID, id); // ?

        String rawId = getJSONString(contact, XContactAccessor.LOGIC_FIELD_RAWID);
        propValue.put(XContactAccessor.LOGIC_FIELD_RAWID, rawId); // ?

        // ? JSON ?
        addName(contact, propValue);
        Iterator<String> fieldItor = XContactAccessor.LOGIC_CONTACT_SINGLE_VALUE_FIELDS.iterator();
        while (fieldItor.hasNext()) {
            transferSingleValueField(contact, propValue, fieldItor.next());
        }

        // ??
        mContactAccessor.initQuantityOfFields(propValue);
        fieldItor = XContactAccessor.LOGIC_CONTACT_MULTIPLE_VALUE_FIELDS.iterator();
        while (fieldItor.hasNext()) {
            transferMultipleValueFields(contact, propValue, fieldItor.next());
        }

        return mContactAccessor.saveContact(propValue);
    }

    /**
     *  Name.
     *
     * @param contact
     *            ? JSON ?
     * @param contactPropMap
     *            ??? map
     * @return ??? map
     */
    private void addName(JSONObject contact, HashMap<String, Object> contactPropMap) {
        String parentField = XContactAccessor.LOGIC_FIELD_NAME;
        JSONObject nameJson = contact.optJSONObject(parentField);

        if (nameJson != null) {
            Iterator<String> logicSubFields = mContactAccessor.getLogicContactSubFieldsOf(parentField).iterator();

            while (logicSubFields.hasNext()) {
                String childField = logicSubFields.next();
                String key = mContactAccessor.generateSubFieldKey(parentField, 0, childField);
                putValueToMap(contactPropMap, key, getJSONString(nameJson, childField));
            }
        }
    }

    /**
     * JSONObject??Hash
     *
     * @param jsonObj
     *            ??json
     * @param contactData
     *            ??hash
     * @param logicField
     *            ????
     */
    private void transferSingleValueField(JSONObject jsonObj, HashMap<String, Object> contactData,
            String logicField) {
        String fieldValue = getJSONString(jsonObj, logicField);
        if (fieldValue != null) {
            contactData.put(logicField, fieldValue);
        }
    }

    /**
     * JSONObjectchildren???Hash
     *
     * @param jsonObj
     *            ??json
     * @param contactData
     *            ??hash
     * @param logicParentField
     *            ??
     */
    private void transferMultipleValueFields(JSONObject jsonObj, HashMap<String, Object> contactData,
            String logicParentField) {
        try {
            JSONArray childrenJson = jsonObj.optJSONArray(logicParentField);
            if (null == childrenJson) {
                return;
            }

            int len = childrenJson.length();
            for (int i = 0; i < len; i++) {
                JSONObject childJson = (JSONObject) childrenJson.get(i);

                ListIterator<String> logicSubFieldItor = mContactAccessor
                        .getLogicContactSubFieldsOf(logicParentField).listIterator();

                while (logicSubFieldItor.hasNext()) {
                    String logicSubField = logicSubFieldItor.next();
                    String key = mContactAccessor.generateSubFieldKey(logicParentField, i, logicSubField);
                    putValueToMap(contactData, key, getJSONString(childJson, logicSubField));
                }

                // photosvalue
                if (XContactAccessor.LOGIC_FIELD_PHOTOS.equals(logicParentField)) {
                    byte[] bytes = getPhotoBytes(getJSONString(jsonObj, XContactAccessor.LOGIC_FIELD_COMMON_VALUE));
                    String key = mContactAccessor.generateSubFieldKey(logicParentField, i,
                            XContactAccessor.LOGIC_FIELD_COMMON_VALUE);
                    contactData.put(key, bytes);
                }
            }

            String quantity = mContactAccessor.generateQuantityFieldOf(logicParentField);
            contactData.put(quantity, len);
        } catch (JSONException e) {
            e.printStackTrace();
            XLog.d(CLASS_NAME, "Could not read field [" + logicParentField + "] from json object");
        }
    }

    /**
     * ??????.
     *
     * @param filename
     *            ?????
     * @return  byte 
     * @throws IOException
     */
    private byte[] getPhotoBytes(String filename) {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try {
            int bytesRead = 0;
            long totalBytesRead = 0;
            byte[] data = new byte[XConstant.BUFFER_LEN * 4];
            InputStream in = getDataStreamFromUri(filename);

            while ((bytesRead = in.read(data, 0, data.length)) != -1 && totalBytesRead <= MAX_PHOTO_SIZE) {
                buffer.write(data, 0, bytesRead);
                totalBytesRead += bytesRead;
            }

            in.close();
            buffer.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            XLog.e(CLASS_NAME, e.getMessage(), e);
        } catch (IOException e) {
            e.printStackTrace();
            XLog.e(CLASS_NAME, e.getMessage(), e);
        }
        return buffer.toByteArray();
    }

    /**
     *  URI?? content://, http://, file://.
     *
     * @param path
     *            
     * @return ?
     * @throws IOException
     */
    private InputStream getDataStreamFromUri(String path) throws IOException {
        if (path.startsWith("content:")) {
            Uri uri = Uri.parse(path);
            ContentResolver resolver = getContext().getContentResolver();
            return resolver.openInputStream(uri);
        } else if (path.startsWith("http:") || path.startsWith("file:")) {
            URL url = new URL(path);
            return url.openStream();
        } else {
            return new FileInputStream(path);
        }
    }

    /**
     *  contactId ???.
     *
     * @param contactId
     *            ????
     * @return ? JSON ?
     * @throws JSONException
     */
    private JSONObject getContactById(String contactId) throws JSONException {
        JSONArray fields = new JSONArray();
        fields.put("*");

        HashSet<String> contactPropertySet = getContactFieldSet(fields);
        Cursor cursor = mContactAccessor.getContactById(contactId);
        JSONArray contacts = populateContactArray(contactPropertySet, cursor, 1);
        cursor.close();

        return (contacts.length() == 1) ? contacts.getJSONObject(0) : null;
    }

    /**
     * HashMapvaluenullkey??
     *
     * @param key
     *            
     * @param value
     *            
     */
    private void putValueToMap(HashMap<String, Object> map, String key, Object value) {
        if (null == map || null == value || XStringUtils.isEmptyString(key)) {
            return;
        }
        if (value instanceof String && XStringUtils.isEmptyString((String) value)) {
            return;
        }
        map.put(key, value);
    }

    /**
     * ?
     * @param callbackCtx
     */
    private void startContactActivity() {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
        mExtensionContext.getSystemContext().startActivityForResult(this, intent, PICK_CONTACT);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (PICK_CONTACT == requestCode) {
            if (resultCode == Activity.RESULT_OK) {
                if (!handleResult(intent)) {
                    handleError(CALL_CHOOSE_ERROR);
                }
            }
        }
    }

    /**
     * ??field
     * @param args:????fields
     */
    private JSONArray getFields(JSONArray args) {
        JSONObject options = null;
        JSONArray fields = null;
        if (null != args) {
            options = args.optJSONObject(0);
        }
        if (null != options) {
            fields = options.optJSONArray(TAG_FIELDS);
            ;
        }
        if (null == fields) {
            fields = new JSONArray();
            /**???*/
            fields.put("*");
        }
        return fields;
    }

    /**
     * ?activity
     * @param intent
     */
    private boolean handleResult(Intent intent) {
        Uri contactData = intent.getData();
        Cursor cursor = getContext().getContentResolver().query(contactData, null, null, null, null);
        if (cursor.moveToFirst()) {
            String contactID = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
            Cursor cursorContact = mContactAccessor.getContactById(contactID);
            JSONArray result = populateContactArray(getContactFieldSet(mFields), cursorContact, Integer.MAX_VALUE);
            return sendJSCallback(result);
        }
        return false;
    }

    /**
     * ??
     * @param contactObject:JSON
     */
    private boolean sendJSCallback(JSONArray contact) {
        if (null != mCallbackCtx) {
            try {
                mCallbackCtx.success(contact.getJSONObject(0));
                return true;
            } catch (JSONException e) {
                XLog.e(CLASS_NAME, e.getMessage());
                handleError(e.getMessage());
            }
        }
        return false;
    }

    /**
     * ?
     */
    private void handleError(String errorMsg) {
        new XNotification(mExtensionContext.getSystemContext()).toast(errorMsg);
    }
}