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