AI2 Component  (Version nb184)
ContactPicker.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2009-2011 Google, All Rights reserved
3 // Copyright 2011-2018 MIT, All rights reserved
4 // Released under the Apache License, Version 2.0
5 // http://www.apache.org/licenses/LICENSE-2.0
6 
7 package com.google.appinventor.components.runtime;
8 
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.List;
12 
13 import android.app.Activity;
14 import android.content.Intent;
15 import android.database.Cursor;
16 import android.net.Uri;
17 import android.provider.Contacts;
18 import android.util.Log;
19 import android.Manifest;
20 
32 
57 @DesignerComponent(version = YaVersion.CONTACTPICKER_COMPONENT_VERSION,
58  description = "A button that, when clicked on, displays a list of " +
59  "the contacts to choose among. After the user has made a " +
60  "selection, the following properties will be set to information about " +
61  "the chosen contact: <ul>\n" +
62  "<li> <code>ContactName</code>: the contact's name </li>\n " +
63  "<li> <code>EmailAddress</code>: the contact's primary email address </li>\n " +
64  "<li> <code>ContactUri</code>: the contact's URI on the device </li>\n"+
65  "<li> <code>EmailAddressList</code>: a list of the contact's email addresses </li>\n " +
66  "<li> <code>PhoneNumber</code>: the contact's primary phone number (on Later Android Verisons)</li>\n " +
67  "<li> <code>PhoneNumberList</code>: a list of the contact's phone numbers (on Later Android Versions)</li>\n " +
68  "<li> <code>Picture</code>: the name of the file containing the contact's " +
69  "image, which can be used as a <code>Picture</code> property value for " +
70  "the <code>Image</code> or <code>ImageSprite</code> component.</li></ul>\n" +
71  "</p><p>Other properties affect the appearance of the button " +
72  "(<code>TextAlignment</code>, <code>BackgroundColor</code>, etc.) and " +
73  "whether it can be clicked on (<code>Enabled</code>).\n</p>" +
74  "<p>The ContactPicker component might not work on all phones. For " +
75  "example, on Android systems before system 3.0, it cannot pick phone " +
76  "numbers, and the list of email addresses will contain only one email.",
77  category = ComponentCategory.SOCIAL)
78 @SimpleObject
79 @UsesPermissions(permissionNames = "android.permission.READ_CONTACTS")
80 public class ContactPicker extends Picker implements ActivityResultListener {
81 
82  private static String[] CONTACT_PROJECTION;
83  private static String[] DATA_PROJECTION;
84  private static final String[] PROJECTION = {
85  Contacts.PeopleColumns.NAME,
86  Contacts.People.PRIMARY_EMAIL_ID,
87  };
88 
89  private static final int NAME_INDEX = 0;
90  private static final int EMAIL_INDEX = 1;
91  private static final int PHONE_INDEX = 2;
92 
93  protected final Activity activityContext;
94  private final Uri intentUri;
95 
96  protected String contactName;
97  protected String emailAddress;
98  protected String contactUri;
99  protected String contactPictureUri;
100  protected String phoneNumber;
101 
102  protected List emailAddressList;
103  protected List phoneNumberList;
104 
105  private boolean havePermission = false; // Do we have READ_CONTACTS permission?
106 
112  public ContactPicker(ComponentContainer container) {
113  this(container, Contacts.People.CONTENT_URI);
114  }
115 
116  protected ContactPicker(ComponentContainer container, Uri intentUri) {
117  super(container);
118  activityContext = container.$context();
119 
120  if (SdkLevel.getLevel() >= SdkLevel.LEVEL_HONEYCOMB_MR1 && intentUri.equals(Contacts.People.CONTENT_URI)) {
121  this.intentUri = HoneycombMR1Util.getContentUri();
122  } else if (SdkLevel.getLevel() >= SdkLevel.LEVEL_HONEYCOMB_MR1 && intentUri.equals(Contacts.Phones.CONTENT_URI)) {
123  this.intentUri = HoneycombMR1Util.getPhoneContentUri();
124  } else {
125  this.intentUri = intentUri;
126  }
127  }
128 
129  @Override
130  public void click() {
131  if (!havePermission) {
132  container.$form()
133  .askPermission(Manifest.permission.READ_CONTACTS,
135  @Override
136  public void HandlePermissionResponse(String permission, boolean granted) {
137  if (granted) {
138  ContactPicker.this.havePermission = true;
139  ContactPicker.this.click();
140  } else {
141  container.$form().dispatchPermissionDeniedEvent(ContactPicker.this,
142  "Click", Manifest.permission.READ_CONTACTS);
143  }
144  }
145  });
146  return;
147  }
148  super.click();
149  }
150 
156  category = PropertyCategory.BEHAVIOR)
157  public String Picture() {
158  return ensureNotNull(contactPictureUri);
159  }
160 
165  category = PropertyCategory.BEHAVIOR)
166  public String ContactName() {
167  return ensureNotNull(contactName);
168  }
169 
175  category = PropertyCategory.BEHAVIOR)
176  public String EmailAddress() {
177  // Note(halabelson): I am commenting out this test. Android provider.Contacts was
178  // deprecated in Donut, but email picking still seems to work on newer versions of the SDK.
179  // If there's a phone where it does not work, we'll get the error at PuntContactSelection
180  // Note that there is still a general problem with contact picking on Motoblur.
181  // if (SdkLevel.getLevel() > SdkLevel.LEVEL_DONUT) {
182  // container.$form().dispatchErrorOccurredEvent(this, "EmailAddress",
183  // ErrorMessages.ERROR_FUNCTIONALITY_NOT_SUPPORTED_CONTACT_EMAIL);
184  // }
185  return ensureNotNull(emailAddress);
186  }
187 
191  @SimpleProperty(description = "URI that specifies the location of the contact on the device.",
192  category = PropertyCategory.BEHAVIOR)
193  public String ContactUri() {
194  return ensureNotNull(contactUri);
195  }
196 
201  category = PropertyCategory.BEHAVIOR)
202  public List EmailAddressList() {
203  return ensureNotNull(emailAddressList);
204  }
205 
211  category = PropertyCategory.BEHAVIOR)
212  public String PhoneNumber() {
213  return ensureNotNull(phoneNumber);
214  }
215 
220  category = PropertyCategory.BEHAVIOR)
221  public List PhoneNumberList() {
222  return ensureNotNull(phoneNumberList);
223  }
224 
228  @SimpleFunction(description = "view a contact via its URI")
229  public void ViewContact(String uri) {
230  if(contactUri != null){
231  Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse(uri));
232  if (intent.resolveActivity(this.activityContext.getPackageManager()) != null) {
233  this.activityContext.startActivity(intent);
234  }
235  }
236  }
237 
238  @Override
239  protected Intent getIntent() {
240  return new Intent(Intent.ACTION_PICK, intentUri);
241  }
242 
251  @Override
252  public void resultReturned(int requestCode, int resultCode, Intent data) {
253  if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) {
254  Log.i("ContactPicker", "received intent is " + data);
255  Uri receivedContactUri = data.getData();
256 
257  // Pre- and post-Honeycomb need different URIs.
258  String desiredContactUri = "";
260  desiredContactUri = "//com.android.contacts/contact";
261  } else {
262  desiredContactUri = "//contacts/people";
263  }
264 
265  if (checkContactUri(receivedContactUri, desiredContactUri)) {
266  Cursor contactCursor = null;
267  Cursor dataCursor = null;
268  try {
270  CONTACT_PROJECTION = HoneycombMR1Util.getContactProjection();
271  contactCursor = activityContext.getContentResolver().query(receivedContactUri,
272  CONTACT_PROJECTION, null, null, null);
273 
274  String id = postHoneycombGetContactNameAndPicture(contactCursor);
275 
276  DATA_PROJECTION = HoneycombMR1Util.getDataProjection();
277  dataCursor = HoneycombMR1Util.getDataCursor(id, activityContext, DATA_PROJECTION);
278  postHoneycombGetContactEmailAndPhone(dataCursor);
279 
280  //explicit set TextContactUri
281  contactUri = receivedContactUri.toString();
282  } else {
283  contactCursor = activityContext.getContentResolver().query(receivedContactUri,
284  PROJECTION, null, null, null);
285  preHoneycombGetContactInfo(contactCursor, receivedContactUri);
286  }
287  Log.i("ContactPicker",
288  "Contact name = " + contactName + ", email address = " + emailAddress + ",contact Uri = " + contactUri +
289  ", phone number = " + phoneNumber + ", contactPhotoUri = " + contactPictureUri);
290  } catch (Exception e) {
291  // There was an exception in trying to extract the cursor from the activity context.
292  // It's bad form to catch an arbitrary exception, but if there is an error here
293  // it's unclear what's going on.
294  Log.i("ContactPicker", "checkContactUri failed: D");
296  } finally {
297  if (contactCursor != null) {
298  contactCursor.close();
299  }
300  if (dataCursor != null) {
301  dataCursor.close();
302  }
303  }
304  } // ends if (checkContactUri ...
305  AfterPicking();
306  } // ends if (requestCode ...
307  }
308 
312  public void preHoneycombGetContactInfo(Cursor contactCursor, Uri theContactUri) {
313  if (contactCursor.moveToFirst()) {
314  contactName = guardCursorGetString(contactCursor, NAME_INDEX);
315  String emailId = guardCursorGetString(contactCursor, EMAIL_INDEX);
316  emailAddress = getEmailAddress(emailId);
317  contactUri = theContactUri.toString();
318  contactPictureUri = theContactUri.toString();
319  emailAddressList = emailAddress.equals("") ? new ArrayList() : Arrays.asList(emailAddress);
320  }
321  }
322 
327  public String postHoneycombGetContactNameAndPicture(Cursor contactCursor) {
328  String id = "";
329  if (contactCursor.moveToFirst()) {
330  final int ID_INDEX = HoneycombMR1Util.getIdIndex(contactCursor);
331  final int NAME_INDEX = HoneycombMR1Util.getNameIndex(contactCursor);
332  final int THUMBNAIL_INDEX = HoneycombMR1Util.getThumbnailIndex(contactCursor);
333  final int PHOTO_INDEX = HoneycombMR1Util.getPhotoIndex(contactCursor);
334  id = guardCursorGetString(contactCursor, ID_INDEX);
335  contactName = guardCursorGetString(contactCursor, NAME_INDEX);
336  contactPictureUri = guardCursorGetString(contactCursor, THUMBNAIL_INDEX);
337 
338  Log.i("ContactPicker", "photo_uri=" + guardCursorGetString(contactCursor, PHOTO_INDEX));
339  }
340  return id;
341  }
342 
347  public void postHoneycombGetContactEmailAndPhone(Cursor dataCursor) {
348  phoneNumber = "";
349  emailAddress = "";
350  List<String> phoneListToStore = new ArrayList<String>();
351  List<String> emailListToStore = new ArrayList<String>();
352 
353  if (dataCursor.moveToFirst()) {
354  final int PHONE_INDEX = HoneycombMR1Util.getPhoneIndex(dataCursor);
355  final int EMAIL_INDEX = HoneycombMR1Util.getEmailIndex(dataCursor);
356  final int MIME_INDEX = HoneycombMR1Util.getMimeIndex(dataCursor);
357 
358  String phoneType = HoneycombMR1Util.getPhoneType();
359  String emailType = HoneycombMR1Util.getEmailType();
360 
361  while (!dataCursor.isAfterLast()) {
362  String type = guardCursorGetString(dataCursor, MIME_INDEX);
363  if (type.contains(phoneType)) {
364  phoneListToStore.add(guardCursorGetString(dataCursor, PHONE_INDEX));
365  } else if (type.contains(emailType)) {
366  emailListToStore.add(guardCursorGetString(dataCursor, EMAIL_INDEX));
367  } else {
368  Log.i("ContactPicker", "Type mismatch: " + type +
369  " not " + phoneType +
370  " or " + emailType);
371  }
372  dataCursor.moveToNext();
373  }
374  }
375 
376  if (!phoneListToStore.isEmpty()) {
377  phoneNumber = phoneListToStore.get(0);
378  }
379  if (!emailListToStore.isEmpty()) {
380  emailAddress = emailListToStore.get(0);
381  }
382  phoneNumberList = phoneListToStore;
383  emailAddressList = emailListToStore;
384 
385  }
386 
387  // Check that the contact URI has the right form to permit the information to be
388  // extracted and try to show a meaningful error notice to the end user of the app.
389  // Sadly, different phones can produce different kinds of URIs. You
390  // can also get a different Uri depending on whether or not the user
391  // does a search to get the contact, versus just picking it. For example,
392  // Motorola Global phones produce an intent whose data part is null.
393  // Or using search on Nexus phones will produce a contact URI of the form
394  // content://com.android.contacts/contact, whereas doing direct selection
395  // produces a Uri have a specific required pattern that is
396  // passed in as an argument.
397  // TODO(halabelson): Create a better set of tests and/or generalize the extraction
398  // methods to permit more URIs.
399  // This should be done in conjunction with updating the way we handle contacts.
400 
401  protected boolean checkContactUri(Uri suspectUri, String requiredPattern) {
402  Log.i("ContactPicker", "contactUri is " + suspectUri);
403  if (suspectUri == null || (!("content".equals(suspectUri.getScheme())))) {
404  Log.i("ContactPicker", "checkContactUri failed: A");
405  puntContactSelection(
407  return false;
408  }
409  String UriSpecific = suspectUri.getSchemeSpecificPart();
410  if (!UriSpecific.startsWith(requiredPattern)) {
411  Log.i("ContactPicker", "checkContactUri failed: C");
412  Log.i("ContactPicker", suspectUri.getPath());
414  return false;
415  } else {
416  return true;
417  }
418  }
419 
420  // set the (supposedly) extracted properties to the empty string and
421  // report an error
422  protected void puntContactSelection(int errorNumber) {
423  contactName = "";
424  emailAddress = "";
425  contactPictureUri = "";
426  container.$form().dispatchErrorOccurredEvent(this, "", errorNumber);
427  }
428 
432  protected String getEmailAddress(String emailId) {
433  int id;
434  try {
435  id = Integer.parseInt(emailId);
436  } catch (NumberFormatException e) {
437  return "";
438  }
439 
440  String data = "";
441  String where = "contact_methods._id = " + id;
442  String[] projection = {
443  Contacts.ContactMethods.DATA
444  };
445  Cursor cursor = activityContext.getContentResolver().query(
446  Contacts.ContactMethods.CONTENT_EMAIL_URI,
447  projection, where, null, null);
448  try {
449  if (cursor.moveToFirst()) {
450  data = guardCursorGetString(cursor, 0);
451  }
452  } finally {
453  cursor.close();
454  }
455  // this extra check for null might be redundant, but we given that there are mysterious errors
456  // on some phones, we'll leave it in just to be extra careful
457  return ensureNotNull(data);
458  }
459 
460 
461  // If the selection returns null, this should be passed back as a
462  // an empty string to prevent errors if the app tries to convert this
463  // to a string. In some cases, getString can also throw an exception, for example,
464  // in selecting the name for a contact where there is no name.
465  // We also call ensureNotNull in the property selectors for ContactName, etc.
466  // This would appear to be redundant, but in testing, there have been some mysterious
467  // error conditions on some phones that permit nulls to sneak through from guardCursonGetString,
468  // so we'll do the extra check.
469 
470  protected String guardCursorGetString(Cursor cursor, int index) {
471  String result;
472  try {
473  result = cursor.getString(index);
474  } catch (Exception e) {
475  // It's bad practice to catch a general exception, but unfortunately,
476  // the exception thrown is implementation dependent, according to the
477  // Android documentation.
478  result = "";
479  }
480  return ensureNotNull(result);
481  }
482 
483  protected String ensureNotNull(String value) {
484  if (value == null) {
485  return "";
486  } else {
487  return value;
488  }
489  }
490 
491  protected List ensureNotNull(List value) {
492  if (value == null) {
493  return new ArrayList();
494  } else {
495  return value;
496  }
497  }
498 }
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getDataCursor
static Cursor getDataCursor(String id, Activity activityContext, String[] dataProjection)
Definition: HoneycombMR1Util.java:192
com.google.appinventor.components.annotations.SimpleFunction
Definition: SimpleFunction.java:23
com.google.appinventor.components.runtime.ContactPicker.postHoneycombGetContactEmailAndPhone
void postHoneycombGetContactEmailAndPhone(Cursor dataCursor)
Definition: ContactPicker.java:347
com.google.appinventor.components.runtime.util.ErrorMessages
Definition: ErrorMessages.java:17
com.google.appinventor.components.runtime.ContactPicker.puntContactSelection
void puntContactSelection(int errorNumber)
Definition: ContactPicker.java:422
com.google.appinventor.components.runtime.util
-*- mode: java; c-basic-offset: 2; -*-
Definition: AccountChooser.java:7
com.google.appinventor.components.runtime.ContactPicker.postHoneycombGetContactNameAndPicture
String postHoneycombGetContactNameAndPicture(Cursor contactCursor)
Definition: ContactPicker.java:327
com.google.appinventor.components.common.YaVersion
Definition: YaVersion.java:14
com.google.appinventor.components.runtime.ContactPicker
Definition: ContactPicker.java:80
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getContentUri
static Uri getContentUri()
Definition: HoneycombMR1Util.java:37
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getDataProjection
static String[] getDataProjection()
Definition: HoneycombMR1Util.java:84
com.google.appinventor.components
com.google.appinventor.components.runtime.util.HoneycombMR1Util
Definition: HoneycombMR1Util.java:29
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getPhotoIndex
static int getPhotoIndex(Cursor contactCursor)
Definition: HoneycombMR1Util.java:139
com.google.appinventor.components.annotations.DesignerComponent
Definition: DesignerComponent.java:22
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER
static final int ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER
Definition: ErrorMessages.java:144
com.google.appinventor.components.annotations.PropertyCategory.BEHAVIOR
BEHAVIOR
Definition: PropertyCategory.java:15
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getEmailIndex
static int getEmailIndex(Cursor dataCursor)
Definition: HoneycombMR1Util.java:147
com.google.appinventor.components.runtime.ContactPicker.ensureNotNull
String ensureNotNull(String value)
Definition: ContactPicker.java:483
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getPhoneContentUri
static Uri getPhoneContentUri()
Definition: HoneycombMR1Util.java:44
com.google.appinventor.components.runtime.ContactPicker.getEmailAddress
String getEmailAddress(String emailId)
Definition: ContactPicker.java:432
com.google.appinventor.components.annotations.UsesPermissions
Definition: UsesPermissions.java:21
com.google.appinventor.components.runtime.ContactPicker.contactPictureUri
String contactPictureUri
Definition: ContactPicker.java:99
com.google.appinventor.components.runtime.ContactPicker.ensureNotNull
List ensureNotNull(List value)
Definition: ContactPicker.java:491
com.google.appinventor.components.runtime.PermissionResultHandler
Definition: PermissionResultHandler.java:15
com.google.appinventor.components.runtime.util.SdkLevel
Definition: SdkLevel.java:19
com.google.appinventor.components.runtime.ContactPicker.ContactPicker
ContactPicker(ComponentContainer container, Uri intentUri)
Definition: ContactPicker.java:116
com.google.appinventor.components.annotations.SimpleProperty
Definition: SimpleProperty.java:23
com.google.appinventor.components.runtime.ContactPicker.contactUri
String contactUri
Definition: ContactPicker.java:98
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getThumbnailIndex
static int getThumbnailIndex(Cursor contactCursor)
Definition: HoneycombMR1Util.java:132
com.google.appinventor.components.annotations.PropertyCategory
Definition: PropertyCategory.java:13
com.google.appinventor.components.runtime.ComponentContainer
Definition: ComponentContainer.java:16
com.google.appinventor.components.runtime.ContactPicker.resultReturned
void resultReturned(int requestCode, int resultCode, Intent data)
Definition: ContactPicker.java:252
com.google.appinventor.components.runtime.ContactPicker.phoneNumberList
List phoneNumberList
Definition: ContactPicker.java:103
com.google.appinventor.components.runtime.util.SdkLevel.getLevel
static int getLevel()
Definition: SdkLevel.java:45
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getPhoneType
static String getPhoneType()
Definition: HoneycombMR1Util.java:157
com.google.appinventor.components.runtime.ContactPicker.activityContext
final Activity activityContext
Definition: ContactPicker.java:93
com.google.appinventor.components.common
Definition: ComponentCategory.java:7
com.google.appinventor.components.common.ComponentCategory
Definition: ComponentCategory.java:48
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getNameIndex
static int getNameIndex(Cursor contactCursor)
Definition: HoneycombMR1Util.java:125
com.google.appinventor.components.runtime.ActivityResultListener
Definition: ActivityResultListener.java:16
com.google.appinventor.components.runtime.Picker
Definition: Picker.java:20
com.google.appinventor.components.runtime.ContactPicker.guardCursorGetString
String guardCursorGetString(Cursor cursor, int index)
Definition: ContactPicker.java:470
com.google.appinventor.components.annotations.SimpleObject
Definition: SimpleObject.java:23
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getEmailType
static String getEmailType()
Definition: HoneycombMR1Util.java:164
com.google
com
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getPhoneIndex
static int getPhoneIndex(Cursor dataCursor)
Definition: HoneycombMR1Util.java:143
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getIdIndex
static int getIdIndex(Cursor contactCursor)
Definition: HoneycombMR1Util.java:111
com.google.appinventor.components.runtime.ContactPicker.contactName
String contactName
Definition: ContactPicker.java:96
com.google.appinventor.components.runtime.ContactPicker.getIntent
Intent getIntent()
Definition: ContactPicker.java:239
com.google.appinventor.components.runtime.ContactPicker.phoneNumber
String phoneNumber
Definition: ContactPicker.java:100
com.google.appinventor.components.runtime.ComponentContainer.$context
Activity $context()
com.google.appinventor.components.runtime.ContactPicker.checkContactUri
boolean checkContactUri(Uri suspectUri, String requiredPattern)
Definition: ContactPicker.java:401
com.google.appinventor.components.runtime.ContactPicker.ContactPicker
ContactPicker(ComponentContainer container)
Definition: ContactPicker.java:112
com.google.appinventor.components.runtime.ContactPicker.click
void click()
Definition: ContactPicker.java:130
com.google.appinventor.components.annotations
com.google.appinventor.components.runtime.ContactPicker.preHoneycombGetContactInfo
void preHoneycombGetContactInfo(Cursor contactCursor, Uri theContactUri)
Definition: ContactPicker.java:312
com.google.appinventor
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getMimeIndex
static int getMimeIndex(Cursor dataCursor)
Definition: HoneycombMR1Util.java:151
com.google.appinventor.components.runtime.util.SdkLevel.LEVEL_HONEYCOMB_MR1
static final int LEVEL_HONEYCOMB_MR1
Definition: SdkLevel.java:29
com.google.appinventor.components.runtime.ContactPicker.emailAddressList
List emailAddressList
Definition: ContactPicker.java:102
com.google.appinventor.components.runtime.util.HoneycombMR1Util.getContactProjection
static String[] getContactProjection()
Definition: HoneycombMR1Util.java:58
com.google.appinventor.components.runtime.ContactPicker.emailAddress
String emailAddress
Definition: ContactPicker.java:97