// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.components.runtime; import com.google.appinventor.components.annotations.DesignerComponent; import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.annotations.UsesPermissions; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.runtime.util.HoneycombMR1Util; import com.google.appinventor.components.runtime.util.ErrorMessages; import com.google.appinventor.components.runtime.util.SdkLevel; import android.app.Activity; import android.content.ContentUris; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.Contacts; import android.util.Log; import java.util.Arrays; import java.util.ArrayList; import java.util.List; /** * Component enabling a user to select a contact's phone number. * * @author sharon@google.com (Sharon Perl) * @author markf@google.com (Mark Friedman) */ @DesignerComponent(version = YaVersion.PHONENUMBERPICKER_COMPONENT_VERSION, description = "A button that, when clicked on, displays a list of " + "the contacts' phone numbers to choose among. After the user has made a " + "selection, the following properties will be set to information about " + "the chosen contact: <ul>\n" + "<li> <code>ContactName</code>: the contact's name </li>\n " + "<li> <code>PhoneNumber</code>: the contact's phone number </li>\n " + "<li> <code>EmailAddress</code>: the contact's email address </li> " + "<li> <code>Picture</code>: the name of the file containing the contact's " + "image, which can be used as a <code>Picture</code> property value for " + "the <code>Image</code> or <code>ImageSprite</code> component.</li></ul>\n" + "</p><p>Other properties affect the appearance of the button " + "(<code>TextAlignment</code>, <code>BackgroundColor</code>, etc.) and " + "whether it can be clicked on (<code>Enabled</code>).</p>\n" + "<p>The PhoneNumberPicker component may not work on all Android " + "devices. For example, on Android systems before system 3.0, the " + "returned lists of phone numbers and email addresses will be empty.\n", category = ComponentCategory.SOCIAL) @SimpleObject @UsesPermissions(permissionNames = "android.permission.READ_CONTACTS") public class PhoneNumberPicker extends ContactPicker { private static String[] NAME_PROJECTION; private static String[] DATA_PROJECTION; private static final String[] PROJECTION = { Contacts.PeopleColumns.NAME, Contacts.PhonesColumns.NUMBER, Contacts.Phones.PERSON_ID, Contacts.People.PRIMARY_EMAIL_ID, }; private static final int NAME_INDEX = 0; private static final int NUMBER_INDEX = 1; private static final int PERSON_INDEX = 2; private static final int EMAIL_INDEX = 3; private static final String LOG_TAG = "PhoneNumberPicker"; /** * Create a new ContactPicker component. * * @param container the parent container. */ public PhoneNumberPicker(ComponentContainer container) { super(container, Contacts.Phones.CONTENT_URI); } /** * PhoneNumber property getter method. */ @SimpleProperty( category = PropertyCategory.BEHAVIOR) public String PhoneNumber() { return ensureNotNull(phoneNumber); } /** * Callback method to get the result returned by the contact picker activity * * @param requestCode a code identifying the request. * @param resultCode a code specifying success or failure of the activity * @param data the returned data, in this case an Intent whose data field * contains the contact's content provider Uri. */ // TODO(halabelson): Rework how the content selection is done to make this overlap // more with Contact Picker. Note that the two components use different intents, so that // the returned URIs are different (contacts/people vs, contacts/phones) // This really should be fixed by updating the way we handle contacts. See the comments // on checkUri in ContactPicker. @Override public void resultReturned(int requestCode, int resultCode, Intent data) { if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) { Log.i(LOG_TAG, "received intent is " + data); Uri phoneUri = data.getData(); String desiredPhoneUri = ""; if (SdkLevel.getLevel() >= SdkLevel.LEVEL_HONEYCOMB_MR1) { desiredPhoneUri = "//com.android.contacts/data"; } else { desiredPhoneUri = "//contacts/phones"; } if (checkContactUri(phoneUri, desiredPhoneUri)) { Cursor contactCursor = null; Cursor dataCursor = null; try { if (SdkLevel.getLevel() >= SdkLevel.LEVEL_HONEYCOMB_MR1) { NAME_PROJECTION = HoneycombMR1Util.getNameProjection(); contactCursor = activityContext.getContentResolver().query(phoneUri, NAME_PROJECTION, null, null, null); String id = postHoneycombGetContactNameAndPicture(contactCursor); DATA_PROJECTION = HoneycombMR1Util.getDataProjection(); dataCursor = HoneycombMR1Util.getDataCursor(id, activityContext, DATA_PROJECTION); postHoneycombGetContactEmailsAndPhones(dataCursor); } else { contactCursor = activityContext.getContentResolver().query(phoneUri, PROJECTION, null, null, null); preHoneycombGetContactInfo(contactCursor); } Log.i(LOG_TAG, "Contact name = " + contactName + ", phone number = " + phoneNumber + ", emailAddress = " + emailAddress + ", contactPhotoUri = " + contactPictureUri); } catch (Exception e) { // There was an exception in trying to compute the cursor from the activity context. // It's bad form to catch an arbitrary exception, but if there is an error here // it's unclear what's going on. Log.e(LOG_TAG, "Exception in resultReturned", e); puntContactSelection(ErrorMessages.ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER); } finally { if (contactCursor != null) { contactCursor.close(); } if (dataCursor != null){ dataCursor.close(); } } } // ends if (checkContactUri ... AfterPicking(); } // ends if (requestCode ... } /** * For versions before Honeycomb, we get all the contact info from the same table. */ public void preHoneycombGetContactInfo(Cursor cursor) { if (cursor.moveToFirst()) { contactName = guardCursorGetString(cursor, NAME_INDEX); phoneNumber = guardCursorGetString(cursor, NUMBER_INDEX); int contactId = cursor.getInt(PERSON_INDEX); Uri cUri = ContentUris.withAppendedId(Contacts.People.CONTENT_URI, contactId); contactPictureUri = cUri.toString(); String emailId = guardCursorGetString(cursor, EMAIL_INDEX); emailAddress = getEmailAddress(emailId); } } /** * Assigns contactName and contactPictureUri for Honeycomb and up. * Returns id for getting emailAddress and phoneNumber. */ public String postHoneycombGetContactNameAndPicture(Cursor contactCursor) { String id = ""; if (contactCursor.moveToFirst()) { final int CONTACT_ID_INDEX = HoneycombMR1Util.getContactIdIndex(contactCursor); final int NAME_INDEX = HoneycombMR1Util.getNameIndex(contactCursor); final int PHOTO_INDEX = HoneycombMR1Util.getThumbnailIndex(contactCursor); final int PHONE_INDEX = HoneycombMR1Util.getPhoneIndex(contactCursor); phoneNumber = guardCursorGetString(contactCursor, PHONE_INDEX); id = guardCursorGetString(contactCursor, CONTACT_ID_INDEX); contactName = guardCursorGetString(contactCursor, NAME_INDEX); contactPictureUri = guardCursorGetString(contactCursor, PHOTO_INDEX); } return id; } /** * Assigns emailAddress, phoneNumber, emailAddressList, and phoneNumberList * for Honeycomb and up. */ public void postHoneycombGetContactEmailsAndPhones(Cursor dataCursor) { List<String> phoneListToStore = new ArrayList<String>(); List<String> emailListToStore = new ArrayList<String>(); if (dataCursor.moveToFirst()) { final int PHONE_INDEX = HoneycombMR1Util.getPhoneIndex(dataCursor); final int EMAIL_INDEX = HoneycombMR1Util.getEmailIndex(dataCursor); final int MIME_INDEX = HoneycombMR1Util.getMimeIndex(dataCursor); String phoneType = HoneycombMR1Util.getPhoneType(); String emailType = HoneycombMR1Util.getEmailType(); // Get the first (default) email and phone number associated with the contact. while (!dataCursor.isAfterLast()) { String type = guardCursorGetString(dataCursor, MIME_INDEX); if (type.contains(phoneType)) { phoneListToStore.add(guardCursorGetString(dataCursor, PHONE_INDEX)); } else if (type.contains(emailType)) { emailListToStore.add(guardCursorGetString(dataCursor, EMAIL_INDEX)); } else { Log.i("ContactPicker", "Type mismatch: " + type + " not " + phoneType + " or " + emailType); } dataCursor.moveToNext(); } phoneNumberList = phoneListToStore; emailAddressList = emailListToStore; if (!emailAddressList.isEmpty()) { emailAddress = (String) emailAddressList.get(0); } else { emailAddress = ""; } } } }