/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed 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.googlecode.android_scripting.facade;
import android.app.Service;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.util.Log;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcOptional;
import com.googlecode.android_scripting.rpc.RpcParameter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Provides access to contacts related functionality.
*/
public class ContactsFacade extends RpcReceiver {
private static final String TAG = "ContactsFacade";
private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
private static final String ERASE_COMPLETE = "ContactsErased";
private final ContentResolver mContentResolver;
private final Service mService;
private final CommonIntentsFacade mCommonIntentsFacade;
private final ContactsStatusReceiver mContactsStatusReceiver;
private final EventFacade mEventFacade;
private Uri mPhoneContent = null;
private String mContactId;
private String mPrimary;
private String mPhoneNumber;
private String mHasPhoneNumber;
public ContactsFacade(FacadeManager manager) {
super(manager);
mService = manager.getService();
mContentResolver = mService.getContentResolver();
mCommonIntentsFacade = manager.getReceiver(CommonIntentsFacade.class);
mContactsStatusReceiver = new ContactsStatusReceiver();
mContentResolver.registerContentObserver(
ContactsContract.Contacts.CONTENT_URI, true, mContactsStatusReceiver);
mEventFacade = manager.getReceiver(EventFacade.class);
try {
// Backward compatibility... get contract stuff using reflection
Class<?> phone = Class.forName("android.provider.ContactsContract$CommonDataKinds$Phone");
mPhoneContent = (Uri) phone.getField("CONTENT_URI").get(null);
mContactId = (String) phone.getField("CONTACT_ID").get(null);
mPrimary = (String) phone.getField("IS_PRIMARY").get(null);
mPhoneNumber = (String) phone.getField("NUMBER").get(null);
mHasPhoneNumber = (String) phone.getField("HAS_PHONE_NUMBER").get(null);
} catch (Exception e) {
Log.e(TAG, "Unable to get field from Contacts Database");
}
}
private Uri getUri(Integer id) {
return ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
}
@Rpc(
description = "Displays a list of contacts to pick from.",
returns = "A map of result values."
)
public Intent contactsDisplayContactPickList() throws JSONException {
return mCommonIntentsFacade.pick(CONTACTS_URI.toString());
}
@Rpc(
description = "Displays a list of phone numbers to pick from.",
returns = "The selected phone number."
)
public String contactsDisplayPhonePickList() throws JSONException {
String phoneNumber = null;
Intent data = mCommonIntentsFacade.pick(CONTACTS_URI.toString());
if (data != null) {
Uri phoneData = data.getData();
Cursor cursor = mService.getContentResolver().query(phoneData, null, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
phoneNumber =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.PhoneLookup.NUMBER));
}
cursor.close();
}
}
return phoneNumber;
}
@Rpc(description = "Returns a List of all possible attributes for contacts.")
public List<String> contactsGetAttributes() {
List<String> attributes = new ArrayList<String>();
Cursor cursor = mContentResolver.query(CONTACTS_URI, null, null, null, null);
if (cursor != null) {
String[] columns = cursor.getColumnNames();
for (int i = 0; i < columns.length; i++) {
attributes.add(columns[i]);
}
cursor.close();
}
return attributes;
}
@Rpc(description = "Returns a List of all contact IDs.")
public List<Integer> contactsGetContactIds() {
List<Integer> ids = new ArrayList<Integer>();
String[] columns = {"_id"};
Cursor cursor = mContentResolver.query(CONTACTS_URI, columns, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
ids.add(cursor.getInt(0));
}
cursor.close();
}
return ids;
}
@Rpc(description = "Returns a List of all contacts.", returns = "a List of contacts as Maps")
public List<JSONObject> contactsGetAllContacts(
@RpcParameter(name = "attributes") @RpcOptional JSONArray attributes) throws JSONException {
List<JSONObject> contacts = new ArrayList<JSONObject>();
String[] columns;
if (attributes == null || attributes.length() == 0) {
// In case no attributes are specified we set the default ones.
columns = new String[] {ContactsContract.Contacts.NAME_RAW_CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME};
} else {
// Convert selected attributes list into usable string list.
columns = new String[attributes.length()];
for (int i = 0; i < attributes.length(); i++) {
columns[i] = attributes.getString(i);
}
}
List<String> queryList = new ArrayList<String>();
for (String s : columns) {
queryList.add(s);
}
String[] query = queryList.toArray(new String[queryList.size()]);
Cursor cursor = mContentResolver.query(CONTACTS_URI, query, null, null, null);
if (cursor != null) {
int idIndex = cursor.getColumnIndex("_id");
while (cursor.moveToNext()) {
String id = cursor.getString(idIndex);
JSONObject message = new JSONObject();
for (int i = 0; i < columns.length; i++) {
String key = columns[i];
String value = cursor.getString(cursor.getColumnIndex(key));
if (mPhoneNumber != null) {
if (key.equals("primary_phone")) {
value = findPhone(id);
}
}
message.put(key, value);
}
contacts.add(message);
}
cursor.close();
}
return contacts;
}
private String findPhone(String id) {
String phoneNumber = null;
if (id == null || id.equals("")) {
return phoneNumber;
}
try {
if (Integer.parseInt(id) > 0) {
Cursor pCur =
mContentResolver.query(
mPhoneContent,
new String[] {mPhoneNumber},
mContactId + " = ? and " + mPrimary + "=1",
new String[] {id},
null);
if (pCur != null) {
pCur.getColumnNames();
while (pCur.moveToNext()) {
phoneNumber = pCur.getString(0);
break;
}
}
pCur.close();
}
} catch (Exception e) {
return null;
}
return phoneNumber;
}
@Rpc(description = "Returns contacts by ID.")
public JSONObject contactsGetContactById(
@RpcParameter(name = "id") Integer id,
@RpcParameter(name = "attributes") @RpcOptional JSONArray attributes)
throws JSONException {
JSONObject contact = null;
Uri uri = getUri(id);
String[] columns;
if (attributes == null || attributes.length() == 0) {
// In case no attributes are specified we set the default ones.
columns = new String[] {"_id", "name", "primary_phone", "primary_email", "type"};
} else {
// Convert selected attributes list into usable string list.
columns = new String[attributes.length()];
for (int i = 0; i < attributes.length(); i++) {
columns[i] = attributes.getString(i);
}
}
Cursor cursor = mContentResolver.query(uri, columns, null, null, null);
if (cursor != null) {
contact = new JSONObject();
cursor.moveToFirst();
for (int i = 0; i < columns.length; i++) {
contact.put(columns[i], cursor.getString(i));
}
cursor.close();
}
return contact;
}
@Rpc(description = "Returns the number of contacts.")
public Integer contactsGetCount() {
Integer count = 0;
Cursor cursor = mContentResolver.query(CONTACTS_URI, null, null, null, null);
if (cursor != null) {
count = cursor.getCount();
cursor.close();
}
return count;
}
private String[] jsonToArray(JSONArray array) throws JSONException {
String[] resultingArray = null;
if (array != null && array.length() > 0) {
resultingArray = new String[array.length()];
for (int i = 0; i < array.length(); i++) {
resultingArray[i] = array.getString(i);
}
}
return resultingArray;
}
private Uri getAllContactsVcardUri() {
Cursor cursor =
mContentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
new String[] {ContactsContract.Contacts.LOOKUP_KEY},
null,
null,
null);
if (cursor == null) {
return null;
}
try {
StringBuilder uriListBuilder = new StringBuilder();
int index = 0;
while (cursor.moveToNext()) {
if (index != 0) {
uriListBuilder.append(':');
}
uriListBuilder.append(cursor.getString(0));
index++;
}
return Uri.withAppendedPath(
ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI, Uri.encode(uriListBuilder.toString()));
} finally {
cursor.close();
}
}
@Rpc(description = "Erase all contacts in phone book.")
public void contactsEraseAll() {
Cursor cursor =
mContentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
new String[] {ContactsContract.Contacts.LOOKUP_KEY},
null,
null,
null);
if (cursor == null) {
return;
}
while (cursor.moveToNext()) {
Uri uri =
Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, cursor.getString(0));
mContentResolver.delete(uri, null, null);
}
mEventFacade.postEvent(ERASE_COMPLETE, null);
return;
}
/**
* Exactly as per <a href=
* "http://developer.android.com/reference/android/content/ContentResolver.html#query%28android.net.Uri,%20java.lang.String[],%20java.lang.String,%20java.lang.String[],%20java.lang.String%29"
* >ContentResolver.query</a>
*/
@Rpc(description = "Content Resolver Query", returns = "result of query as Maps")
public List<JSONObject> contactsQueryContent(
@RpcParameter(
name = "uri",
description = "The URI, using the content:// scheme, for the content to retrieve."
)
String uri,
@RpcParameter(
name = "attributes",
description = "A list of which columns to return. Passing null will return all columns"
)
@RpcOptional
JSONArray attributes,
@RpcParameter(name = "selection", description = "A filter declaring which rows to return")
@RpcOptional
String selection,
@RpcParameter(
name = "selectionArgs",
description =
"You may include ?s in selection, which will be replaced by the values from selectionArgs"
)
@RpcOptional
JSONArray selectionArgs,
@RpcParameter(name = "order", description = "How to order the rows") @RpcOptional
String order)
throws JSONException {
List<JSONObject> queryResults = new ArrayList<JSONObject>();
String[] columns = jsonToArray(attributes);
String[] args = jsonToArray(selectionArgs);
Cursor cursor = mContentResolver.query(Uri.parse(uri), columns, selection, args, order);
if (cursor != null) {
String[] names = cursor.getColumnNames();
while (cursor.moveToNext()) {
JSONObject message = new JSONObject();
for (int i = 0; i < cursor.getColumnCount(); i++) {
String key = names[i];
String value = cursor.getString(i);
message.put(key, value);
}
queryResults.add(message);
}
cursor.close();
}
return queryResults;
}
@Rpc(
description = "Content Resolver Query Attributes",
returns = "a list of available columns for a given content uri"
)
public JSONArray queryAttributes(
@RpcParameter(
name = "uri",
description = "The URI, using the content:// scheme, for the content to retrieve."
)
String uri)
throws JSONException {
JSONArray columns = new JSONArray();
Cursor cursor = mContentResolver.query(Uri.parse(uri), null, "1=0", null, null);
if (cursor != null) {
String[] names = cursor.getColumnNames();
for (String name : names) {
columns.put(name);
}
cursor.close();
}
return columns;
}
@Rpc(description = "Launches VCF import.")
public void importVcf(
@RpcParameter(
name = "uri",
description = "The URI, using the file:/// scheme, for the content to retrieve."
)
String uri) {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(
new ComponentName(
"com.google.android.contacts",
"com.google.android.apps.contacts.vcard.ImportVCardActivity"));
intent.setData(Uri.parse(uri));
mService.startActivity(intent);
}
@Rpc(description = "Launches VCF export.")
public void exportVcf(
@RpcParameter(
name = "path",
description = "The file path, using the / scheme, for the content to save to."
)
String path) {
OutputStream out = null;
StringBuilder string = new StringBuilder();
try {
AssetFileDescriptor fd =
mContentResolver.openAssetFileDescriptor(getAllContactsVcardUri(), "r");
FileInputStream inputStream = fd.createInputStream();
PrintWriter writer = new PrintWriter(path, "UTF-8");
int character;
while ((character = inputStream.read()) != -1) {
if ((char) character != '\r') {
string.append((char) character);
}
}
writer.append(string);
writer.close();
} catch (IOException e) {
Log.w(TAG, "Failed to export VCF.");
}
}
private class ContactsStatusReceiver extends ContentObserver {
public ContactsStatusReceiver() {
super(null);
}
public void onChange(boolean updated) {
mEventFacade.postEvent("ContactsChanged", null);
}
}
@Override
public void shutdown() {
mContentResolver.unregisterContentObserver(mContactsStatusReceiver);
}
}