A sample application that demonstrates use of legacy and current contacts APIs.

The application provides two implementations for access to contacts, one based
on legacy API, the other based on the current API.  The correct implementation
is chosen at runtime based on the version of the SDK.

Change-Id: Iae0bc8564be420ff504a5dc760b935630a7664e6
diff --git a/samples/BusinessCard/Android.mk b/samples/BusinessCard/Android.mk
new file mode 100644
index 0000000..627cac1
--- /dev/null
+++ b/samples/BusinessCard/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := BusinessCard
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/samples/BusinessCard/AndroidManifest.xml b/samples/BusinessCard/AndroidManifest.xml
new file mode 100644
index 0000000..186e249
--- /dev/null
+++ b/samples/BusinessCard/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.businesscard">
+    
+    <!-- IMPORTANT!  We want this app to run on Cupcake, Donut, Eclair and beyond -->
+    <uses-sdk android:minSdkVersion="3"/>
+    
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    
+    <application android:label="@string/businesscard_app">
+        <activity android:name="BusinessCardActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/samples/BusinessCard/res/layout/business_card.xml b/samples/BusinessCard/res/layout/business_card.xml
new file mode 100644
index 0000000..c7ce713
--- /dev/null
+++ b/samples/BusinessCard/res/layout/business_card.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+    <Button
+        android:id="@+id/pick_contact_button"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_margin="10dip"
+        android:text="@string/button_pick_contact"/>
+    <TextView
+        android:id="@+id/display_name_text_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+    <TextView
+        android:id="@+id/phone_number_text_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/display_name_text_view"
+        android:layout_centerHorizontal="true"
+        android:textAppearance="?android:attr/textAppearanceMedium"/>
+</RelativeLayout>
+
diff --git a/samples/BusinessCard/res/values/strings.xml b/samples/BusinessCard/res/values/strings.xml
new file mode 100644
index 0000000..8668d57
--- /dev/null
+++ b/samples/BusinessCard/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This file contains resource definitions for displayed strings, allowing
+     them to be changed based on the locale and options. -->
+
+<resources>
+    <string name="businesscard_app">Business Card</string>
+    <string name="button_pick_contact">Pick Contact</string>
+</resources>
+
diff --git a/samples/BusinessCard/src/com/example/android/businesscard/BusinessCardActivity.java b/samples/BusinessCard/src/com/example/android/businesscard/BusinessCardActivity.java
new file mode 100644
index 0000000..d8d277e
--- /dev/null
+++ b/samples/BusinessCard/src/com/example/android/businesscard/BusinessCardActivity.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.businesscard;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * A simple activity that shows a "Pick Contact" button and two fields: contact's name
+ * and phone number.  The user taps on the Pick Contact button to bring up
+ * the contact chooser.  Once this activity receives the result from contact picker,
+ * it launches an asynchronous query (queries should always be asynchronous) to load
+ * contact's name and phone number. When the query completes, the activity displays
+ * the loaded data.
+ */
+public class BusinessCardActivity extends Activity  {
+
+    // Request code for the contact picker activity
+    private static final int PICK_CONTACT_REQUEST = 1;
+
+    /**
+     * An SDK-specific instance of {@link ContactAccessor}.  The activity does not need
+     * to know what SDK it is running in: all idiosyncrasies of different SDKs are
+     * encapsulated in the implementations of the ContactAccessor class.
+     */
+    private final ContactAccessor mContactAccessor = ContactAccessor.getInstance();
+
+    /**
+     * Called with the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.business_card);
+
+        // Install a click handler on the Pick Contact button
+        Button pickContact = (Button)findViewById(R.id.pick_contact_button);
+        pickContact.setOnClickListener(new OnClickListener() {
+
+            public void onClick(View v) {
+                pickContact();
+            }
+        });
+    }
+
+    /**
+     * Click handler for the Pick Contact button.  Invokes a contact picker activity.
+     * The specific intent used to bring up that activity differs between versions
+     * of the SDK, which is why we delegate the creation of the intent to ContactAccessor.
+     */
+    protected void pickContact() {
+        startActivityForResult(mContactAccessor.getPickContactIntent(), PICK_CONTACT_REQUEST);
+    }
+
+    /**
+     * Invoked when the contact picker activity is finished. The {@code contactUri} parameter
+     * will contain a reference to the contact selected by the user. We will treat it as
+     * an opaque URI and allow the SDK-specific ContactAccessor to handle the URI accordingly.
+     */
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == PICK_CONTACT_REQUEST && resultCode == RESULT_OK) {
+            loadContactInfo(data.getData());
+        }
+    }
+
+    /**
+     * Load contact information on a background thread.
+     */
+    private void loadContactInfo(Uri contactUri) {
+
+        /*
+         * We should always run database queries on a background thread. The database may be
+         * locked by some process for a long time.  If we locked up the UI thread while waiting
+         * for the query to come back, we might get an "Application Not Responding" dialog.
+         */
+        AsyncTask<Uri, Void, ContactInfo> task = new AsyncTask<Uri, Void, ContactInfo>() {
+
+            @Override
+            protected ContactInfo doInBackground(Uri... uris) {
+                return mContactAccessor.loadContact(getContentResolver(), uris[0]);
+            }
+
+            @Override
+            protected void onPostExecute(ContactInfo result) {
+                bindView(result);
+            }
+        };
+
+        task.execute(contactUri);
+    }
+
+    /**
+     * Displays contact information: name and phone number.
+     */
+    protected void bindView(ContactInfo contactInfo) {
+        TextView displayNameView = (TextView) findViewById(R.id.display_name_text_view);
+        displayNameView.setText(contactInfo.getDisplayName());
+
+        TextView phoneNumberView = (TextView) findViewById(R.id.phone_number_text_view);
+        phoneNumberView.setText(contactInfo.getPhoneNumber());
+    }
+}
diff --git a/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessor.java b/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessor.java
new file mode 100644
index 0000000..6a402e9
--- /dev/null
+++ b/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.businesscard;
+
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+
+/**
+ * This abstract class defines SDK-independent API for communication with
+ * Contacts Provider. The actual implementation used by the application depends
+ * on the level of API available on the device. If the API level is Cupcake or
+ * Donut, we want to use the {@link ContactAccessorSdk3_4} class. If it is
+ * Eclair or higher, we want to use {@link ContactAccessorSdk5}.
+ */
+public abstract class ContactAccessor {
+
+    /**
+     * Static singleton instance of {@link ContactAccessor} holding the
+     * SDK-specific implementation of the class.
+     */
+    private static ContactAccessor sInstance;
+
+    public static ContactAccessor getInstance() {
+        if (sInstance == null) {
+            String className;
+
+            /*
+             * Check the version of the SDK we are running on. Choose an
+             * implementation class designed for that version of the SDK.
+             *
+             * Unfortunately we have to use strings to represent the class
+             * names. If we used the conventional ContactAccessorSdk5.class.getName()
+             * syntax, we would get a ClassNotFoundException at runtime on pre-Eclair SDKs.
+             * Using the above syntax would force Dalvik to load the class and try to
+             * resolve references to all other classes it uses. Since the pre-Eclair
+             * does not have those classes, the loading of ContactAccessorSdk5 would fail.
+             */
+            @SuppressWarnings("deprecation")
+            int sdkVersion = Integer.parseInt(Build.VERSION.SDK);       // Cupcake style
+            if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
+                className = "com.example.android.businesscard.ContactAccessorSdk3_4";
+            } else {
+                className = "com.example.android.businesscard.ContactAccessorSdk5";
+            }
+
+            /*
+             * Find the required class by name and instantiate it.
+             */
+            try {
+                Class<? extends ContactAccessor> clazz =
+                        Class.forName(className).asSubclass(ContactAccessor.class);
+                sInstance = clazz.newInstance();
+            } catch (Exception e) {
+                throw new IllegalStateException(e);
+            }
+        }
+
+        return sInstance;
+    }
+
+    /**
+     * Returns the {@link Intent#ACTION_PICK} intent configured for the right authority: legacy
+     * or current.
+     */
+    public abstract Intent getPickContactIntent();
+
+    /**
+     * Loads contact data for the supplied URI. The actual queries will differ for different APIs
+     * used, but the result is the same: the {@link #mDisplayName} and {@link #mPhoneNumber}
+     * fields are populated with correct data.
+     */
+    public abstract ContactInfo loadContact(ContentResolver contentResolver, Uri contactUri);
+}
diff --git a/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessorSdk3_4.java b/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessorSdk3_4.java
new file mode 100644
index 0000000..7fcd388
--- /dev/null
+++ b/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessorSdk3_4.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.businesscard;
+
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Contacts.People;
+import android.provider.Contacts.People.Phones;
+
+/**
+ * An implementation of {@link ContactAccessor} that uses legacy Contacts API.
+ * These APIs are deprecated and should not be used unless we are running on a
+ * pre-Eclair SDK.
+ * <p>
+ * There are several reasons why we wouldn't want to use this class on an Eclair device:
+ * <ul>
+ * <li>It would see at most one account, namely the first Google account created on the device.
+ * <li>It would work through a compatibility layer, which would make it inherently less efficient.
+ * <li>Not relevant to this particular example, but it would not have access to new kinds
+ * of data available through current APIs.
+ * </ul>
+ */
+@SuppressWarnings("deprecation")
+public class ContactAccessorSdk3_4 extends ContactAccessor {
+
+    /**
+     * Returns a Pick Contact intent using the pre-Eclair "people" URI.
+     */
+    @Override
+    public Intent getPickContactIntent() {
+        return new Intent(Intent.ACTION_PICK, People.CONTENT_URI);
+    }
+
+    /**
+     * Retrieves the contact information.
+     */
+    @Override
+    public ContactInfo loadContact(ContentResolver contentResolver, Uri contactUri) {
+        ContactInfo contactInfo = new ContactInfo();
+        Cursor cursor = contentResolver.query(contactUri,
+                new String[]{People.DISPLAY_NAME}, null, null, null);
+        try {
+            if (cursor.moveToFirst()) {
+                contactInfo.setDisplayName(cursor.getString(0));
+            }
+        } finally {
+            cursor.close();
+        }
+
+        Uri phoneUri = Uri.withAppendedPath(contactUri, Phones.CONTENT_DIRECTORY);
+        cursor = contentResolver.query(phoneUri,
+                new String[]{Phones.NUMBER}, null, null, Phones.ISPRIMARY + " DESC");
+
+        try {
+            if (cursor.moveToFirst()) {
+                contactInfo.setPhoneNumber(cursor.getString(0));
+            }
+        } finally {
+            cursor.close();
+        }
+
+        return contactInfo;
+    }
+}
diff --git a/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessorSdk5.java b/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessorSdk5.java
new file mode 100644
index 0000000..6855597
--- /dev/null
+++ b/samples/BusinessCard/src/com/example/android/businesscard/ContactAccessorSdk5.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.businesscard;
+
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+
+/**
+ * An implementation of {@link ContactAccessor} that uses current Contacts API.
+ * This class should be used on Eclair or beyond, but would not work on any earlier
+ * release of Android.  As a matter of fact, it could not even be loaded.
+ * <p>
+ * This implementation has several advantages:
+ * <ul>
+ * <li>It sees contacts from multiple accounts.
+ * <li>It works with aggregated contacts. So for example, if the contact is the result
+ * of aggregation of two raw contacts from different accounts, it may return the name from
+ * one and the phone number from the other.
+ * <li>It is efficient because it uses the more efficient current API.
+ * <li>Not obvious in this particular example, but it has access to new kinds
+ * of data available exclusively through the new APIs. Exercise for the reader: add support
+ * for nickname (see {@link android.provider.ContactsContract.CommonDataKinds.Nickname}) or
+ * social status updates (see {@link android.provider.ContactsContract.StatusUpdates}).
+ * </ul>
+ */
+public class ContactAccessorSdk5 extends ContactAccessor {
+
+    /**
+     * Returns a Pick Contact intent using the Eclair "contacts" URI.
+     */
+    @Override
+    public Intent getPickContactIntent() {
+        return new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+    }
+
+    /**
+     * Retrieves the contact information.
+     */
+    @Override
+    public ContactInfo loadContact(ContentResolver contentResolver, Uri contactUri) {
+        ContactInfo contactInfo = new ContactInfo();
+        long contactId = -1;
+
+        // Load the display name for the specified person
+        Cursor cursor = contentResolver.query(contactUri,
+                new String[]{Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
+        try {
+            if (cursor.moveToFirst()) {
+                contactId = cursor.getLong(0);
+                contactInfo.setDisplayName(cursor.getString(1));
+            }
+        } finally {
+            cursor.close();
+        }
+
+        // Load the phone number (if any).
+        cursor = contentResolver.query(Phone.CONTENT_URI,
+                new String[]{Phone.NUMBER},
+                Phone.CONTACT_ID + "=" + contactId, null, Phone.IS_SUPER_PRIMARY + " DESC");
+        try {
+            if (cursor.moveToFirst()) {
+                contactInfo.setPhoneNumber(cursor.getString(0));
+            }
+        } finally {
+            cursor.close();
+        }
+
+        return contactInfo;
+    }
+}
diff --git a/samples/BusinessCard/src/com/example/android/businesscard/ContactInfo.java b/samples/BusinessCard/src/com/example/android/businesscard/ContactInfo.java
new file mode 100644
index 0000000..61a6f3b
--- /dev/null
+++ b/samples/BusinessCard/src/com/example/android/businesscard/ContactInfo.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.businesscard;
+
+/**
+ * A model object containing contact data.
+ */
+public class ContactInfo {
+
+    private String mDisplayName;
+    private String mPhoneNumber;
+
+    public void setDisplayName(String displayName) {
+        this.mDisplayName = displayName;
+    }
+
+    public String getDisplayName() {
+        return mDisplayName;
+    }
+
+    public void setPhoneNumber(String phoneNumber) {
+        this.mPhoneNumber = phoneNumber;
+    }
+
+    public String getPhoneNumber() {
+        return mPhoneNumber;
+    }
+}