1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.android_scripting.facade; 18 19 import android.app.Service; 20 import android.content.ContentResolver; 21 import android.content.ContentUris; 22 import android.content.Intent; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.provider.ContactsContract; 26 27 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 28 import com.googlecode.android_scripting.rpc.Rpc; 29 import com.googlecode.android_scripting.rpc.RpcOptional; 30 import com.googlecode.android_scripting.rpc.RpcParameter; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 35 import org.json.JSONArray; 36 import org.json.JSONException; 37 import org.json.JSONObject; 38 39 /** 40 * Provides access to contacts related functionality. 41 * 42 * @author MeanEYE.rcf (meaneye.rcf (at) gmail.com 43 */ 44 public class ContactsFacade extends RpcReceiver { 45 private static final Uri CONTACTS_URI = Uri.parse("content://contacts/people"); 46 private final ContentResolver mContentResolver; 47 private final Service mService; 48 private final CommonIntentsFacade mCommonIntentsFacade; 49 public Uri mPhoneContent = null; 50 public String mContactId; 51 public String mPrimary; 52 public String mPhoneNumber; 53 public String mHasPhoneNumber; 54 55 public ContactsFacade(FacadeManager manager) { 56 super(manager); 57 mService = manager.getService(); 58 mContentResolver = mService.getContentResolver(); 59 mCommonIntentsFacade = manager.getReceiver(CommonIntentsFacade.class); 60 try { 61 // Backward compatibility... get contract stuff using reflection 62 Class<?> phone = Class.forName("android.provider.ContactsContract$CommonDataKinds$Phone"); 63 mPhoneContent = (Uri) phone.getField("CONTENT_URI").get(null); 64 mContactId = (String) phone.getField("CONTACT_ID").get(null); 65 mPrimary = (String) phone.getField("IS_PRIMARY").get(null); 66 mPhoneNumber = (String) phone.getField("NUMBER").get(null); 67 mHasPhoneNumber = (String) phone.getField("HAS_PHONE_NUMBER").get(null); 68 } catch (Exception e) { 69 } 70 } 71 72 private Uri buildUri(Integer id) { 73 Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id); 74 return uri; 75 } 76 77 @Rpc(description = "Displays a list of contacts to pick from.", returns = "A map of result values.") 78 public Intent pickContact() throws JSONException { 79 return mCommonIntentsFacade.pick("content://contacts/people"); 80 } 81 82 @Rpc(description = "Displays a list of phone numbers to pick from.", returns = "The selected phone number.") 83 public String pickPhone() throws JSONException { 84 String result = null; 85 Intent data = mCommonIntentsFacade.pick("content://contacts/phones"); 86 if (data != null) { 87 Uri phoneData = data.getData(); 88 Cursor cursor = mService.getContentResolver().query(phoneData, null, null, null, null); 89 if (cursor != null) { 90 if (cursor.moveToFirst()) { 91 result = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.PhoneLookup.NUMBER)); 92 } 93 cursor.close(); 94 } 95 } 96 return result; 97 } 98 99 @Rpc(description = "Returns a List of all possible attributes for contacts.") 100 public List<String> contactsGetAttributes() { 101 List<String> result = new ArrayList<String>(); 102 Cursor cursor = mContentResolver.query(CONTACTS_URI, null, null, null, null); 103 if (cursor != null) { 104 String[] columns = cursor.getColumnNames(); 105 for (int i = 0; i < columns.length; i++) { 106 result.add(columns[i]); 107 } 108 cursor.close(); 109 } 110 return result; 111 } 112 113 // TODO(MeanEYE.rcf): Add ability to narrow selection by providing named pairs of attributes. 114 @Rpc(description = "Returns a List of all contact IDs.") 115 public List<Integer> contactsGetIds() { 116 List<Integer> result = new ArrayList<Integer>(); 117 String[] columns = { "_id" }; 118 Cursor cursor = mContentResolver.query(CONTACTS_URI, columns, null, null, null); 119 if (cursor != null) { 120 while (cursor.moveToNext()) { 121 result.add(cursor.getInt(0)); 122 } 123 cursor.close(); 124 } 125 return result; 126 } 127 128 @Rpc(description = "Returns a List of all contacts.", returns = "a List of contacts as Maps") 129 public List<JSONObject> contactsGet( 130 @RpcParameter(name = "attributes") @RpcOptional JSONArray attributes) throws JSONException { 131 List<JSONObject> result = new ArrayList<JSONObject>(); 132 String[] columns; 133 if (attributes == null || attributes.length() == 0) { 134 // In case no attributes are specified we set the default ones. 135 columns = new String[] { "_id", "name", "primary_phone", "primary_email", "type" }; 136 } else { 137 // Convert selected attributes list into usable string list. 138 columns = new String[attributes.length()]; 139 for (int i = 0; i < attributes.length(); i++) { 140 columns[i] = attributes.getString(i); 141 } 142 } 143 List<String> queryList = new ArrayList<String>(); 144 for (String s : columns) { 145 queryList.add(s); 146 } 147 if (!queryList.contains("_id")) { 148 queryList.add("_id"); 149 } 150 151 String[] query = queryList.toArray(new String[queryList.size()]); 152 Cursor cursor = mContentResolver.query(CONTACTS_URI, query, null, null, null); 153 if (cursor != null) { 154 int idIndex = cursor.getColumnIndex("_id"); 155 while (cursor.moveToNext()) { 156 String id = cursor.getString(idIndex); 157 JSONObject message = new JSONObject(); 158 for (int i = 0; i < columns.length; i++) { 159 String key = columns[i]; 160 String value = cursor.getString(cursor.getColumnIndex(key)); 161 if (mPhoneNumber != null) { 162 if (key.equals("primary_phone")) { 163 value = findPhone(id); 164 } 165 } 166 message.put(key, value); 167 } 168 result.add(message); 169 } 170 cursor.close(); 171 } 172 return result; 173 } 174 175 private String findPhone(String id) { 176 String result = null; 177 if (id == null || id.equals("")) { 178 return result; 179 } 180 try { 181 if (Integer.parseInt(id) > 0) { 182 Cursor pCur = 183 mContentResolver.query(mPhoneContent, new String[] { mPhoneNumber }, mContactId 184 + " = ? and " + mPrimary + "=1", new String[] { id }, null); 185 if (pCur != null) { 186 pCur.getColumnNames(); 187 while (pCur.moveToNext()) { 188 result = pCur.getString(0); 189 break; 190 } 191 } 192 pCur.close(); 193 } 194 } catch (Exception e) { 195 return null; 196 } 197 return result; 198 } 199 200 @Rpc(description = "Returns contacts by ID.") 201 public JSONObject contactsGetById(@RpcParameter(name = "id") Integer id, 202 @RpcParameter(name = "attributes") @RpcOptional JSONArray attributes) throws JSONException { 203 JSONObject result = null; 204 Uri uri = buildUri(id); 205 String[] columns; 206 if (attributes == null || attributes.length() == 0) { 207 // In case no attributes are specified we set the default ones. 208 columns = new String[] { "_id", "name", "primary_phone", "primary_email", "type" }; 209 } else { 210 // Convert selected attributes list into usable string list. 211 columns = new String[attributes.length()]; 212 for (int i = 0; i < attributes.length(); i++) { 213 columns[i] = attributes.getString(i); 214 } 215 } 216 Cursor cursor = mContentResolver.query(uri, columns, null, null, null); 217 if (cursor != null) { 218 result = new JSONObject(); 219 cursor.moveToFirst(); 220 for (int i = 0; i < columns.length; i++) { 221 result.put(columns[i], cursor.getString(i)); 222 } 223 cursor.close(); 224 } 225 return result; 226 } 227 228 // TODO(MeanEYE.rcf): Add ability to narrow selection by providing named pairs of attributes. 229 @Rpc(description = "Returns the number of contacts.") 230 public Integer contactsGetCount() { 231 Integer result = 0; 232 Cursor cursor = mContentResolver.query(CONTACTS_URI, null, null, null, null); 233 if (cursor != null) { 234 result = cursor.getCount(); 235 cursor.close(); 236 } 237 return result; 238 } 239 240 private String[] jsonToArray(JSONArray array) throws JSONException { 241 String[] result = null; 242 if (array != null && array.length() > 0) { 243 result = new String[array.length()]; 244 for (int i = 0; i < array.length(); i++) { 245 result[i] = array.getString(i); 246 } 247 } 248 return result; 249 } 250 251 /** 252 * Exactly as per <a href= 253 * "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" 254 * >ContentResolver.query</a> 255 */ 256 @Rpc(description = "Content Resolver Query", returns = "result of query as Maps") 257 public List<JSONObject> queryContent( 258 @RpcParameter(name = "uri", description = "The URI, using the content:// scheme, for the content to retrieve.") String uri, 259 @RpcParameter(name = "attributes", description = "A list of which columns to return. Passing null will return all columns") @RpcOptional JSONArray attributes, 260 @RpcParameter(name = "selection", description = "A filter declaring which rows to return") @RpcOptional String selection, 261 @RpcParameter(name = "selectionArgs", description = "You may include ?s in selection, which will be replaced by the values from selectionArgs") @RpcOptional JSONArray selectionArgs, 262 @RpcParameter(name = "order", description = "How to order the rows") @RpcOptional String order) 263 throws JSONException { 264 List<JSONObject> result = new ArrayList<JSONObject>(); 265 String[] columns = jsonToArray(attributes); 266 String[] args = jsonToArray(selectionArgs); 267 Cursor cursor = mContentResolver.query(Uri.parse(uri), columns, selection, args, order); 268 if (cursor != null) { 269 String[] names = cursor.getColumnNames(); 270 while (cursor.moveToNext()) { 271 JSONObject message = new JSONObject(); 272 for (int i = 0; i < cursor.getColumnCount(); i++) { 273 String key = names[i]; 274 String value = cursor.getString(i); 275 message.put(key, value); 276 } 277 result.add(message); 278 } 279 cursor.close(); 280 } 281 return result; 282 } 283 284 @Rpc(description = "Content Resolver Query Attributes", returns = "a list of available columns for a given content uri") 285 public JSONArray queryAttributes( 286 @RpcParameter(name = "uri", description = "The URI, using the content:// scheme, for the content to retrieve.") String uri) 287 throws JSONException { 288 JSONArray result = new JSONArray(); 289 Cursor cursor = mContentResolver.query(Uri.parse(uri), null, "1=0", null, null); 290 if (cursor != null) { 291 String[] names = cursor.getColumnNames(); 292 for (String name : names) { 293 result.put(name); 294 } 295 cursor.close(); 296 } 297 return result; 298 } 299 300 @Override 301 public void shutdown() { 302 } 303 } 304