• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.googlecode.android_scripting.facade;
18 
19 import android.app.Service;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.Intent;
24 import android.content.res.AssetFileDescriptor;
25 import android.database.ContentObserver;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.provider.ContactsContract;
29 import android.util.Log;
30 
31 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
32 import com.googlecode.android_scripting.rpc.Rpc;
33 import com.googlecode.android_scripting.rpc.RpcOptional;
34 import com.googlecode.android_scripting.rpc.RpcParameter;
35 
36 import java.io.FileInputStream;
37 import java.io.IOException;
38 import java.io.OutputStream;
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.List;
42 
43 import org.json.JSONArray;
44 import org.json.JSONException;
45 import org.json.JSONObject;
46 
47 /**
48  * Provides access to contacts related functionality.
49  */
50 public class ContactsFacade extends RpcReceiver {
51   private static final String TAG = "ContactsFacade";
52   private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
53   private static final String ERASE_COMPLETE = "ContactsErased";
54   private final ContentResolver mContentResolver;
55   private final Service mService;
56   private final CommonIntentsFacade mCommonIntentsFacade;
57   private final ContactsStatusReceiver mContactsStatusReceiver;
58   private final EventFacade mEventFacade;
59 
60   private Uri mPhoneContent = null;
61   private String mContactId;
62   private String mPrimary;
63   private String mPhoneNumber;
64   private String mHasPhoneNumber;
65 
ContactsFacade(FacadeManager manager)66   public ContactsFacade(FacadeManager manager) {
67     super(manager);
68     mService = manager.getService();
69     mContentResolver = mService.getContentResolver();
70     mCommonIntentsFacade = manager.getReceiver(CommonIntentsFacade.class);
71     mContactsStatusReceiver = new ContactsStatusReceiver();
72     mContentResolver.registerContentObserver(
73         ContactsContract.Contacts.CONTENT_URI, true, mContactsStatusReceiver);
74     mEventFacade = manager.getReceiver(EventFacade.class);
75     try {
76       // Backward compatibility... get contract stuff using reflection
77       Class<?> phone = Class.forName("android.provider.ContactsContract$CommonDataKinds$Phone");
78       mPhoneContent = (Uri) phone.getField("CONTENT_URI").get(null);
79       mContactId = (String) phone.getField("CONTACT_ID").get(null);
80       mPrimary = (String) phone.getField("IS_PRIMARY").get(null);
81       mPhoneNumber = (String) phone.getField("NUMBER").get(null);
82       mHasPhoneNumber = (String) phone.getField("HAS_PHONE_NUMBER").get(null);
83     } catch (Exception e) {
84         Log.e(TAG, "Unable to get field from Contacts Database");
85     }
86   }
87 
getUri(Integer id)88   private Uri getUri(Integer id) {
89       return ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
90   }
91 
92   @Rpc(
93     description = "Displays a list of contacts to pick from.",
94     returns = "A map of result values."
95   )
contactsDisplayContactPickList()96   public Intent contactsDisplayContactPickList() throws JSONException {
97     return mCommonIntentsFacade.pick(CONTACTS_URI.toString());
98   }
99 
100   @Rpc(
101     description = "Displays a list of phone numbers to pick from.",
102     returns = "The selected phone number."
103   )
contactsDisplayPhonePickList()104   public String contactsDisplayPhonePickList() throws JSONException {
105     String phoneNumber = null;
106     Intent data = mCommonIntentsFacade.pick(CONTACTS_URI.toString());
107     if (data != null) {
108       Uri phoneData = data.getData();
109       Cursor cursor = mService.getContentResolver().query(phoneData, null, null, null, null);
110       if (cursor != null) {
111         if (cursor.moveToFirst()) {
112           phoneNumber =
113               cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.PhoneLookup.NUMBER));
114         }
115         cursor.close();
116       }
117     }
118     return phoneNumber;
119   }
120 
121   @Rpc(description = "Returns a List of all possible attributes for contacts.")
contactsGetAttributes()122   public List<String> contactsGetAttributes() {
123     List<String> attributes = new ArrayList<String>();
124     Cursor cursor = mContentResolver.query(CONTACTS_URI, null, null, null, null);
125     if (cursor != null) {
126       String[] columns = cursor.getColumnNames();
127       for (int i = 0; i < columns.length; i++) {
128         attributes.add(columns[i]);
129       }
130       cursor.close();
131     }
132     return attributes;
133   }
134 
135   @Rpc(description = "Returns a List of all contact IDs.")
contactsGetContactIds()136   public List<Integer> contactsGetContactIds() {
137     List<Integer> ids = new ArrayList<Integer>();
138     String[] columns = {"_id"};
139     Cursor cursor = mContentResolver.query(CONTACTS_URI, columns, null, null, null);
140     if (cursor != null) {
141       while (cursor.moveToNext()) {
142         ids.add(cursor.getInt(0));
143       }
144       cursor.close();
145     }
146     return ids;
147   }
148 
149   @Rpc(description = "Returns a List of all contacts.", returns = "a List of contacts as Maps")
contactsGetAllContacts( @pcParametername = "attributes") @pcOptional JSONArray attributes)150   public List<JSONObject> contactsGetAllContacts(
151       @RpcParameter(name = "attributes") @RpcOptional JSONArray attributes) throws JSONException {
152     List<JSONObject> contacts = new ArrayList<JSONObject>();
153     String[] columns;
154     if (attributes == null || attributes.length() == 0) {
155       // In case no attributes are specified we set the default ones.
156       columns = new String[] {ContactsContract.Contacts.NAME_RAW_CONTACT_ID,
157               ContactsContract.Contacts.DISPLAY_NAME};
158     } else {
159       // Convert selected attributes list into usable string list.
160       columns = new String[attributes.length()];
161       for (int i = 0; i < attributes.length(); i++) {
162         columns[i] = attributes.getString(i);
163       }
164     }
165     List<String> queryList = new ArrayList<String>();
166     for (String s : columns) {
167       queryList.add(s);
168     }
169 
170     String[] query = queryList.toArray(new String[queryList.size()]);
171     Cursor cursor = mContentResolver.query(CONTACTS_URI, query, null, null, null);
172     if (cursor != null) {
173       int idIndex = cursor.getColumnIndex("_id");
174       while (cursor.moveToNext()) {
175         String id = cursor.getString(idIndex);
176         JSONObject message = new JSONObject();
177         for (int i = 0; i < columns.length; i++) {
178           String key = columns[i];
179           String value = cursor.getString(cursor.getColumnIndex(key));
180           if (mPhoneNumber != null) {
181             if (key.equals("primary_phone")) {
182               value = findPhone(id);
183             }
184           }
185           message.put(key, value);
186         }
187         contacts.add(message);
188       }
189       cursor.close();
190     }
191     return contacts;
192   }
193 
findPhone(String id)194   private String findPhone(String id) {
195     String phoneNumber = null;
196     if (id == null || id.equals("")) {
197       return phoneNumber;
198     }
199     try {
200       if (Integer.parseInt(id) > 0) {
201         Cursor pCur =
202             mContentResolver.query(
203                 mPhoneContent,
204                 new String[] {mPhoneNumber},
205                 mContactId + " = ? and " + mPrimary + "=1",
206                 new String[] {id},
207                 null);
208         if (pCur != null) {
209           pCur.getColumnNames();
210           while (pCur.moveToNext()) {
211             phoneNumber = pCur.getString(0);
212             break;
213           }
214         }
215         pCur.close();
216       }
217     } catch (Exception e) {
218       return null;
219     }
220     return phoneNumber;
221   }
222 
223   @Rpc(description = "Returns contacts by ID.")
contactsGetContactById( @pcParametername = "id") Integer id, @RpcParameter(name = "attributes") @RpcOptional JSONArray attributes)224   public JSONObject contactsGetContactById(
225       @RpcParameter(name = "id") Integer id,
226       @RpcParameter(name = "attributes") @RpcOptional JSONArray attributes)
227       throws JSONException {
228     JSONObject contact = null;
229     Uri uri = getUri(id);
230     String[] columns;
231     if (attributes == null || attributes.length() == 0) {
232       // In case no attributes are specified we set the default ones.
233       columns = new String[] {"_id", "name", "primary_phone", "primary_email", "type"};
234     } else {
235       // Convert selected attributes list into usable string list.
236       columns = new String[attributes.length()];
237       for (int i = 0; i < attributes.length(); i++) {
238         columns[i] = attributes.getString(i);
239       }
240     }
241     Cursor cursor = mContentResolver.query(uri, columns, null, null, null);
242     if (cursor != null) {
243       contact = new JSONObject();
244       cursor.moveToFirst();
245       for (int i = 0; i < columns.length; i++) {
246         contact.put(columns[i], cursor.getString(i));
247       }
248       cursor.close();
249     }
250     return contact;
251   }
252 
253   @Rpc(description = "Returns the number of contacts.")
contactsGetCount()254   public Integer contactsGetCount() {
255     Integer count = 0;
256     Cursor cursor = mContentResolver.query(CONTACTS_URI, null, null, null, null);
257     if (cursor != null) {
258       count = cursor.getCount();
259       cursor.close();
260     }
261     return count;
262   }
263 
jsonToArray(JSONArray array)264   private String[] jsonToArray(JSONArray array) throws JSONException {
265     String[] resultingArray = null;
266     if (array != null && array.length() > 0) {
267       resultingArray = new String[array.length()];
268       for (int i = 0; i < array.length(); i++) {
269           resultingArray[i] = array.getString(i);
270       }
271     }
272     return resultingArray;
273   }
274 
getAllContactsVcardUri()275   private Uri getAllContactsVcardUri() {
276     Cursor cursor =
277         mContentResolver.query(
278             ContactsContract.Contacts.CONTENT_URI,
279             new String[] {ContactsContract.Contacts.LOOKUP_KEY},
280             null,
281             null,
282             null);
283     if (cursor == null) {
284       return null;
285     }
286     try {
287       StringBuilder uriListBuilder = new StringBuilder();
288       int index = 0;
289       while (cursor.moveToNext()) {
290         if (index != 0) {
291           uriListBuilder.append(':');
292         }
293         uriListBuilder.append(cursor.getString(0));
294         index++;
295       }
296       return Uri.withAppendedPath(
297           ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI, Uri.encode(uriListBuilder.toString()));
298     } finally {
299       cursor.close();
300     }
301   }
302 
303   @Rpc(description = "Erase all contacts in phone book.")
contactsEraseAll()304   public void contactsEraseAll() {
305     Cursor cursor =
306         mContentResolver.query(
307             ContactsContract.Contacts.CONTENT_URI,
308             new String[] {ContactsContract.Contacts.LOOKUP_KEY},
309             null,
310             null,
311             null);
312     if (cursor == null) {
313       return;
314     }
315     while (cursor.moveToNext()) {
316       Uri uri =
317           Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, cursor.getString(0));
318       mContentResolver.delete(uri, null, null);
319     }
320     mEventFacade.postEvent(ERASE_COMPLETE, null);
321     return;
322   }
323 
324   /**
325    * Exactly as per <a href=
326    * "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"
327    * >ContentResolver.query</a>
328    */
329   @Rpc(description = "Content Resolver Query", returns = "result of query as Maps")
contactsQueryContent( @pcParameter 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)330   public List<JSONObject> contactsQueryContent(
331       @RpcParameter(
332             name = "uri",
333             description = "The URI, using the content:// scheme, for the content to retrieve."
334           )
335           String uri,
336       @RpcParameter(
337             name = "attributes",
338             description = "A list of which columns to return. Passing null will return all columns"
339           )
340           @RpcOptional
341           JSONArray attributes,
342       @RpcParameter(name = "selection", description = "A filter declaring which rows to return")
343           @RpcOptional
344           String selection,
345       @RpcParameter(
346             name = "selectionArgs",
347             description =
348                 "You may include ?s in selection, which will be replaced by the values from selectionArgs"
349           )
350           @RpcOptional
351           JSONArray selectionArgs,
352       @RpcParameter(name = "order", description = "How to order the rows") @RpcOptional
353           String order)
354       throws JSONException {
355     List<JSONObject> queryResults = new ArrayList<JSONObject>();
356     String[] columns = jsonToArray(attributes);
357     String[] args = jsonToArray(selectionArgs);
358     Cursor cursor = mContentResolver.query(Uri.parse(uri), columns, selection, args, order);
359     if (cursor != null) {
360       String[] names = cursor.getColumnNames();
361       while (cursor.moveToNext()) {
362         JSONObject message = new JSONObject();
363         for (int i = 0; i < cursor.getColumnCount(); i++) {
364           String key = names[i];
365           String value = cursor.getString(i);
366           message.put(key, value);
367         }
368         queryResults.add(message);
369       }
370       cursor.close();
371     }
372     return queryResults;
373   }
374 
375   @Rpc(
376     description = "Content Resolver Query Attributes",
377     returns = "a list of available columns for a given content uri"
378   )
queryAttributes( @pcParameter name = "uri", description = "The URI, using the content:// scheme, for the content to retrieve." ) String uri)379   public JSONArray queryAttributes(
380       @RpcParameter(
381             name = "uri",
382             description = "The URI, using the content:// scheme, for the content to retrieve."
383           )
384           String uri)
385       throws JSONException {
386     JSONArray columns = new JSONArray();
387     Cursor cursor = mContentResolver.query(Uri.parse(uri), null, "1=0", null, null);
388     if (cursor != null) {
389       String[] names = cursor.getColumnNames();
390       for (String name : names) {
391         columns.put(name);
392       }
393       cursor.close();
394     }
395     return columns;
396   }
397 
398   @Rpc(description = "Launches VCF import.")
importVcf( @pcParameter name = "uri", description = "The URI, using the file:/// scheme, for the content to retrieve." ) String uri)399   public void importVcf(
400       @RpcParameter(
401             name = "uri",
402             description = "The URI, using the file:/// scheme, for the content to retrieve."
403           )
404           String uri) {
405     Intent intent = new Intent();
406     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
407     intent.setComponent(
408         new ComponentName(
409             "com.google.android.contacts",
410             "com.google.android.apps.contacts.vcard.ImportVCardActivity"));
411     intent.setData(Uri.parse(uri));
412     mService.startActivity(intent);
413   }
414 
415   @Rpc(description = "Launches VCF export.")
exportVcf( @pcParameter name = "path", description = "The file path, using the / scheme, for the content to save to." ) String path)416   public void exportVcf(
417       @RpcParameter(
418             name = "path",
419             description = "The file path, using the / scheme, for the content to save to."
420           )
421           String path) {
422     OutputStream out = null;
423     StringBuilder string = new StringBuilder();
424     try {
425       AssetFileDescriptor fd =
426           mContentResolver.openAssetFileDescriptor(getAllContactsVcardUri(), "r");
427       FileInputStream inputStream = fd.createInputStream();
428       PrintWriter writer = new PrintWriter(path, "UTF-8");
429       int character;
430       while ((character = inputStream.read()) != -1) {
431         if ((char) character != '\r') {
432           string.append((char) character);
433         }
434       }
435       writer.append(string);
436       writer.close();
437     } catch (IOException e) {
438       Log.w(TAG, "Failed to export VCF.");
439     }
440   }
441 
442   private class ContactsStatusReceiver extends ContentObserver {
ContactsStatusReceiver()443     public ContactsStatusReceiver() {
444       super(null);
445     }
446 
onChange(boolean updated)447     public void onChange(boolean updated) {
448       mEventFacade.postEvent("ContactsChanged", null);
449     }
450   }
451 
452   @Override
shutdown()453   public void shutdown() {
454       mContentResolver.unregisterContentObserver(mContactsStatusReceiver);
455   }
456 }
457