Java tutorial
/* 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); } }