Voicemail provider sample source code.

The app demostrates the how to use voicemail provider APIs to insert new
voicemail records using a simple UI.

Change-Id: I22610e06a8b80518b9f0d811f72afe245a4148c6
diff --git a/build/sdk.atree b/build/sdk.atree
index a4f4d76..688154c 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -180,6 +180,7 @@
 development/samples/USB/MissileLauncher        samples/${PLATFORM_NAME}/USB/MissileLauncher
 development/samples/USB/AdbTest                samples/${PLATFORM_NAME}/USB/AdbTest
 development/samples/VoiceRecognitionService    samples/${PLATFORM_NAME}/VoiceRecognitionService
+development/samples/VoicemailProviderDemo      samples/${PLATFORM_NAME}/VoicemailProviderDemo
 development/samples/WeatherListWidget          samples/${PLATFORM_NAME}/WeatherListWidget
 development/apps/WidgetPreview                 samples/${PLATFORM_NAME}/WidgetPreview
 development/samples/Wiktionary                 samples/${PLATFORM_NAME}/Wiktionary
diff --git a/samples/VoicemailProviderDemo/Android.mk b/samples/VoicemailProviderDemo/Android.mk
new file mode 100644
index 0000000..60bf14d
--- /dev/null
+++ b/samples/VoicemailProviderDemo/Android.mk
@@ -0,0 +1,32 @@
+#
+# Copyright (C) 2011 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.
+#
+
+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 := VoicemailProviderDemo
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/VoicemailProviderDemo/AndroidManifest.xml b/samples/VoicemailProviderDemo/AndroidManifest.xml
new file mode 100644
index 0000000..7dd0a0c
--- /dev/null
+++ b/samples/VoicemailProviderDemo/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2011 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.
+-->
+
+<!-- Declare the contents of this Android application.  The namespace
+     attribute brings in the Android platform namespace, and the package
+     supplies a unique name for the application.  When writing your
+     own application, the package name must be changed from "com.example.*"
+     to come from a domain that you own or have control over. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    android:versionCode="1" android:versionName="1.0" package="com.example.android.voicemail">
+
+    <uses-sdk android:minSdkVersion="9" />
+    <uses-sdk android:targetSdkVersion="9" />
+
+    <uses-permission android:name="com.android.providers.voicemail.permission.READ_WRITE_OWN_VOICEMAIL" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <application android:icon="@drawable/icon" android:label="@string/app_name">
+        <activity android:name=".AddVoicemailActivity" android:label="@string/app_name">
+            <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/VoicemailProviderDemo/MODULE_LICENSE_APACHE2 b/samples/VoicemailProviderDemo/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/samples/VoicemailProviderDemo/MODULE_LICENSE_APACHE2
diff --git a/samples/VoicemailProviderDemo/NOTICE b/samples/VoicemailProviderDemo/NOTICE
new file mode 100644
index 0000000..becc120
--- /dev/null
+++ b/samples/VoicemailProviderDemo/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2011, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/samples/VoicemailProviderDemo/_index.html b/samples/VoicemailProviderDemo/_index.html
new file mode 100644
index 0000000..27a9e49
--- /dev/null
+++ b/samples/VoicemailProviderDemo/_index.html
@@ -0,0 +1,47 @@
+<p>
+This is a simple sample application that demonstrates how to use voicemail
+content provider APIs to insert new voicemail records.
+</p>
+<p>
+The application includes
+ <a href="src/com/example/android/voicemail/AddVoicemailActivity.html">
+    <code>AddVoicemailActivity</code>
+ </a>,
+an activity that lets the user enter voicemail details and record voicemail audio,
+which can then be stored with the voicemail content provider by tapping the "Send"
+button.
+<p>
+<img alt="Add voicemail" src="../images/VoicemailProviderDemo.png"
+ width=250px/>
+</p>
+
+<p>
+In the real world, a similar application could download voicemails from a
+remote voicemail server and store them locally with the voicemail content
+provider. The platform would then take care of notification and rendering of the
+voicemails.
+</p>
+<p>
+Following interfaces are of particular interest:
+<ul>
+   <li>
+     <a href="src/com/example/android/voicemail/common/core/VoicemailProviderHelper.html">
+      <code>VoicemailProviderHelper</code>
+     </a> and its implementation in
+     <a href="src/com/example/android/voicemail/common/core/VoicemailProviderHelpers.html">
+      <code>VoicemailProviderHelpers</code>
+     </a>.
+     This interface provides a good demonstration of various fields exposed by voicemail
+     content provider and their usage.
+   </li>
+   <li>
+     <a href="src/com/example/android/voicemail/common/core/Voicemail.html">
+       <code>Voicemail</code>
+     </a> and its implementation in
+    <a href="src/com/example/android/voicemail/common/core/VoicemailImpl.html">
+      <code>VoicemailImpl</code> </a>.
+      This interface provides a structured view of most the important fields in
+      voicemail content provider.
+   </li>
+ </ul>
+</p>
diff --git a/samples/VoicemailProviderDemo/res/drawable-hdpi/icon.png b/samples/VoicemailProviderDemo/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/samples/VoicemailProviderDemo/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/samples/VoicemailProviderDemo/res/drawable-ldpi/icon.png b/samples/VoicemailProviderDemo/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/samples/VoicemailProviderDemo/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/samples/VoicemailProviderDemo/res/drawable-mdpi/icon.png b/samples/VoicemailProviderDemo/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/samples/VoicemailProviderDemo/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/samples/VoicemailProviderDemo/res/layout/add_voicemail.xml b/samples/VoicemailProviderDemo/res/layout/add_voicemail.xml
new file mode 100644
index 0000000..8b2f52d
--- /dev/null
+++ b/samples/VoicemailProviderDemo/res/layout/add_voicemail.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2011 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/add_voicemail"/>
+
+    <EditText android:id = "@+id/sender_number"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/sender_number"
+        android:inputType="phone"/>
+    <EditText android:id = "@+id/time"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/time"/>
+    <EditText android:id = "@+id/duration"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/duration"
+        android:inputType="number"/>
+    <EditText android:id = "@+id/mime_type"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/mime_type"/>
+    <EditText android:id = "@+id/provider_package"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"/>
+
+    <!-- Recording button -->
+    <Button android:id = "@+id/start_recording_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/record_voice"/>
+
+    <!-- Save button -->
+    <Button android:id = "@+id/save_btn"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/send"/>
+</LinearLayout>
diff --git a/samples/VoicemailProviderDemo/res/values/strings.xml b/samples/VoicemailProviderDemo/res/values/strings.xml
new file mode 100644
index 0000000..250a6b7
--- /dev/null
+++ b/samples/VoicemailProviderDemo/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2011 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.
+-->
+
+<resources>
+    <string name="app_name">VoicemaiProviderDemo</string>
+    <!-- AddVoicemailActivity -->
+    <string name="add_voicemail">Add voicemail</string>
+    <string name="sender_number">Sender phone number</string>
+    <string name="time">Time in DD/MM/YYYY hh:mm</string>
+    <string name="duration">Duration in seconds</string>
+    <string name="mime_type">MIME type</string>
+    <string name="voicemail_store_error">Error in storing voicemail!</string>
+    <string name="send">Send</string>
+    <string name="record_voice">Record voice</string>
+</resources>
diff --git a/samples/VoicemailProviderDemo/src/com/android/providers/voicemail/api/VoicemailProvider.java b/samples/VoicemailProviderDemo/src/com/android/providers/voicemail/api/VoicemailProvider.java
new file mode 100644
index 0000000..4d8876f
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/android/providers/voicemail/api/VoicemailProvider.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 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.android.providers.voicemail.api;
+
+import android.content.Intent;
+import android.net.Uri;
+
+/**
+ * Defines the constants needed to access and interact with the voicemail content provider.
+ */
+public class VoicemailProvider {
+    /** The authority used by the voicemail provider. */
+    public static final String AUTHORITY =
+            "com.android.providers.voicemail";
+
+    /** The main URI exposed by the service. */
+    public static final Uri CONTENT_URI =
+            Uri.parse("content://" + AUTHORITY + "/voicemail");
+    /** The URI to fetch an individual voicemail. */
+    public static final Uri CONTENT_URI_ID_QUERY =
+            Uri.parse("content://" + AUTHORITY + "/voicemail/");
+    /** The URI to fetch all voicemails from a given provider. */
+    public static final Uri CONTENT_URI_PROVIDER_QUERY =
+            Uri.parse("content://" + AUTHORITY + "/voicemail/provider/");
+    /** The URI to fetch an individual voicemail from a given provider. */
+    public static final Uri CONTENT_URI_PROVIDER_ID_QUERY =
+            Uri.parse("content://" + AUTHORITY + "/voicemail/provider/");
+
+    /** Broadcast intent when a new voicemail record is inserted. */
+    public static final String ACTION_NEW_VOICEMAIL = "android.intent.action.NEW_VOICEMAIL";
+    /**
+     * Extra included in {@value Intent#ACTION_PROVIDER_CHANGED} and {@value #ACTION_NEW_VOICEMAIL}
+     * broadcast intents to indicate the package that caused the change in content provider.
+     * <p>
+     * Receivers of the broadcast can use this field to determine if this is a self change.
+     */
+    public static final String EXTRA_CHANGED_BY =
+          "com.android.providers.voicemail.changed_by";
+
+    /** The different tables defined by the content provider. */
+    public static final class Tables {
+        /** The table containing voicemail information. */
+        public static final class Voicemails {
+            public static final String NAME = "voicemails";
+
+            /** The mime type for a collection of voicemails. */
+            public static final String DIR_TYPE =
+                    "vnd.android.cursor.dir/voicemails";
+
+            /** The different columns contained within the voicemail table. */
+            public static final class Columns {
+                public static final String _ID = "_id";
+                public static final String _DATA = "_data";
+                public static final String _DATA_FILE_EXISTS = "_data_file_exists";
+                public static final String NUMBER = "number";
+                public static final String DATE = "date";
+                public static final String DURATION = "duration";
+                public static final String PROVIDER = "provider";
+                public static final String PROVIDER_DATA = "provider_data";
+                public static final String DATA_MIME_TYPE = "data_mime_type";
+                public static final String READ_STATUS = "read_status";
+                /**
+                 * Current mailbox state of the message.
+                 * <p>
+                 * Legal values: 0(Inbox)/1(Deleted)/2(Undeleted).
+                 */
+                public static final String STATE = "state";
+            }
+        }
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/AddVoicemailActivity.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/AddVoicemailActivity.java
new file mode 100644
index 0000000..2a6f8fe
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/AddVoicemailActivity.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2011 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.voicemail;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Pair;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.example.android.voicemail.common.core.Voicemail;
+import com.example.android.voicemail.common.core.VoicemailImpl;
+import com.example.android.voicemail.common.core.VoicemailProviderHelper;
+import com.example.android.voicemail.common.core.VoicemailProviderHelpers;
+import com.example.android.voicemail.common.inject.InjectView;
+import com.example.android.voicemail.common.inject.Injector;
+import com.example.android.voicemail.common.logging.Logger;
+import com.example.android.voicemail.common.ui.DialogHelperImpl;
+import com.example.android.voicemail.common.utils.CloseUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * A simple activity that stores user entered voicemail data into voicemail content provider. To be
+ * used as a test voicemail source.
+ */
+public class AddVoicemailActivity extends Activity {
+    private static final Logger logger = Logger.getLogger(AddVoicemailActivity.class);
+
+    private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("dd/MM/yyyy h:mm");
+
+    private static final int REQUEST_CODE_RECORDING = 100;
+
+    private final DialogHelperImpl mDialogHelper = new DialogHelperImpl(this);
+    /**
+     * This is created in {@link #onCreate(Bundle)}, and needs to be released in
+     * {@link #onDestroy()}.
+     */
+    private VoicemailProviderHelper mVoicemailProviderHelper;
+    private Uri mRecordingUri;
+
+    // Mark the views as injectable. These objects are instantiated automatically during
+    // onCreate() by finding the appropriate view that matches the specified resource_id.
+    @InjectView(R.id.start_recording_btn)
+    private Button mStartRec;
+    @InjectView(R.id.save_btn)
+    private Button mSaveButton;
+    @InjectView(R.id.time)
+    private TextView mTime;
+    @InjectView(R.id.provider_package)
+    private TextView mProviderPackage;
+    @InjectView(R.id.mime_type)
+    private TextView mMimeType;
+    @InjectView(R.id.sender_number)
+    private TextView mSenderNumber;
+    @InjectView(R.id.duration)
+    private TextView mDuration;
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.add_voicemail);
+        // Inject all objects that are marked by @InjectView annotation.
+        Injector.get(this).inject();
+        mVoicemailProviderHelper = VoicemailProviderHelpers.createPackageScopedVoicemailProvider(this);
+
+        setDefaultValues();
+
+        // Record voice button.
+        mStartRec.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                startRecording();
+            }
+        });
+
+        // Save voicemail button.
+        mSaveButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                storeVoicemail();
+            }
+
+        });
+    }
+
+    private void storeVoicemail() {
+        try {
+            Pair<Voicemail, Uri> newVoicemail = new Pair<Voicemail, Uri>(
+                    buildVoicemailObjectFromUiElements(), mRecordingUri);
+            new InsertVoicemailTask().execute(newVoicemail);
+        } catch (ParseException e) {
+            handleError(e);
+        }
+    }
+
+    private Voicemail buildVoicemailObjectFromUiElements() throws ParseException {
+        String sender = mSenderNumber.getText().toString();
+        String dateStr = mTime.getText().toString();
+        String durationStr = mDuration.getText().toString();
+        String mimeType = mMimeType.getText().toString();
+        String sourcePackageName = mProviderPackage.getText().toString();
+        long time = DATE_FORMATTER.parse(dateStr.trim()).getTime();
+        long duration = durationStr.length() != 0 ? Long.parseLong(durationStr) : 0;
+        return VoicemailImpl.createForInsertion(time, sender)
+                .setDuration(duration)
+                .setSource(sourcePackageName)
+                .build();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        switch (requestCode) {
+            case REQUEST_CODE_RECORDING:
+                handleRecordingResult(resultCode, data);
+                break;
+            default:
+                logger.e("onActivityResult: Unexpected requestCode: " + requestCode);
+        }
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, Bundle bundle) {
+        return mDialogHelper.handleOnCreateDialog(id, bundle);
+    }
+
+    /** Set default values in the display */
+    private void setDefaultValues() {
+        // Set time to current time.
+        mTime.setText(DATE_FORMATTER.format(new Date()));
+
+        // Set provider package to this app's package.
+        mProviderPackage.setText(getPackageName());
+    }
+
+    private void startRecording() {
+        Intent recordingIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
+        startActivityForResult(recordingIntent, REQUEST_CODE_RECORDING);
+    }
+
+    private void handleRecordingResult(int resultCode, Intent data) {
+        if (resultCode != RESULT_OK) {
+            handleError(new Exception("Failed to do recording. Error Code: " + resultCode));
+        }
+
+        Uri uri = data.getData();
+        logger.d("Received recording URI: " + uri);
+        if (uri != null) {
+            mRecordingUri = uri;
+            mMimeType.setText(getContentResolver().getType(uri));
+        }
+    }
+
+    private void handleError(Exception e) {
+        mDialogHelper.showErrorMessageDialog(R.string.voicemail_store_error, e);
+    }
+
+    /**
+     * An async task that inserts a new voicemail record using a background thread.
+     * The tasks accepts a pair of voicemail object and the recording Uri as the param.
+     * The result returned is the error exception, if any, encountered during the operation.
+     */
+    private class InsertVoicemailTask extends AsyncTask<Pair<Voicemail, Uri>, Void, Exception> {
+        @Override
+        protected Exception doInBackground(Pair<Voicemail, Uri>... params) {
+            if (params.length > 0) {
+                try {
+                    insertVoicemail(params[0].first, params[0].second);
+                } catch (IOException e) {
+                    return e;
+                }
+            }
+            return null;
+        }
+
+        private void insertVoicemail(Voicemail voicemail, Uri recordingUri) throws IOException {
+            InputStream inputAudioStream = recordingUri == null ? null :
+                  getContentResolver().openInputStream(recordingUri);
+            Uri newVoicemailUri = mVoicemailProviderHelper.insert(voicemail);
+            logger.i("Inserted new voicemail URI: " + newVoicemailUri);
+            if (inputAudioStream != null) {
+                OutputStream outputStream = null;
+                try {
+                    outputStream = mVoicemailProviderHelper.setVoicemailContent(
+                            newVoicemailUri, getContentResolver().getType(recordingUri));
+                    copyStreamData(inputAudioStream, outputStream);
+                } finally {
+                    CloseUtils.closeQuietly(outputStream);
+                    CloseUtils.closeQuietly(inputAudioStream);
+                }
+            }
+        }
+
+        @Override
+        protected void onPostExecute(Exception error) {
+            if (error == null) {
+                // No error - done.
+                finish();
+            } else {
+                handleError(error);
+            }
+        }
+
+        private void copyStreamData(InputStream in, OutputStream out) throws IOException {
+            // Copy 8K chunk at a time.
+            byte[] data = new byte[8 * 1024];
+            int numBytes;
+            while ((numBytes = in.read(data)) > 0) {
+                out.write(data, 0, numBytes);
+            }
+        }
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/Voicemail.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/Voicemail.java
new file mode 100644
index 0000000..f48122b
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/Voicemail.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+import android.net.Uri;
+
+/**
+ * Represents a single voicemail stored in the voicemail content provider.
+ * <p>
+ * The presence of a field is indicated by a corresponding 'has' method.
+ */
+public interface Voicemail {
+    /**
+     * Which mailbox the message is sitting in.
+     * <p>
+     * Note that inbox and deleted are alone insufficient, because we may have a provider that is
+     * not able to undelete (re-upload) a message. Thus we need a state to represent the (common)
+     * case where the user has deleted a message (which results in the message being removed from
+     * the server) and then restored the message (where we are unable to re-upload the message to
+     * the server). That's what the undeleted state is for.
+     * <p>
+     * The presence of an undeleted mailbox prevents the voicemail source from having to keep a list
+     * of all such deleted-then-restored message ids, without which it would be unable to tell the
+     * difference between a message that has been deleted-then-restored by the user and a message
+     * which has been deleted on the server and should now be removed (for example one removed via
+     * an IVR).
+     */
+    public enum Mailbox {
+        /** After being fetched from the server, a message usually starts in the inbox. */
+        INBOX(0),
+        /** Indicates that a message has been deleted. */
+        DELETED(1),
+        /** Restored from having been deleted, distinct from being in the inbox. */
+        UNDELETED(2);
+
+        private final int mValue;
+
+        private Mailbox(int value) {
+            mValue = value;
+        }
+
+        /** Returns the DB value of this mailbox state. */
+        public int getValue() {
+            return mValue;
+        }
+    }
+
+    /**
+     * The identifier of the voicemail in the content provider.
+     * <p>
+     * This may be missing in the case of a new {@link Voicemail} that we plan to insert into the
+     * content provider, since until it has been inserted we don't know what id it should have. If
+     * none is specified, we return -1.
+     */
+    public long getId();
+
+    public boolean hasId();
+
+    /** The number of the person leaving the voicemail, empty string if unknown, null if not set. */
+    public String getNumber();
+
+    public boolean hasNumber();
+
+    /** The timestamp the voicemail was received, in millis since the epoch, zero if not set. */
+    public long getTimestampMillis();
+
+    public boolean hasTimestampMillis();
+
+    /** Gets the duration of the voicemail in millis, or zero if the field is not set. */
+    public long getDuration();
+
+    public boolean hasDuration();
+
+    /**
+     * Returns the package name of the source that added this voicemail, or null if this field is
+     * not set.
+     */
+    public String getSource();
+
+    public boolean hasSource();
+
+    /**
+     * Returns the provider-specific data type stored with the voicemail, or null if this field is
+     * not set.
+     * <p>
+     * Provider data is typically used as an identifier to uniquely identify the voicemail against
+     * the voicemail server. This is likely to be something like the IMAP UID, or some other
+     * server-generated identifying string.
+     */
+    // TODO:4: we should rename the provider data field to be called provider message id, which is
+    // more explicit. I think we should also rename the get id method to get content id or something
+    // like that.
+    public String getProviderData();
+
+    public boolean hasProviderData();
+
+    /**
+     * Gets the Uri that can be used to refer to this voicemail, and to make it play.
+     * <p>
+     * Returns null if we don't know the Uri.
+     */
+    public Uri getUri();
+
+    public boolean hasUri();
+
+    /** Tells us which mailbox the message is sitting in, returns null if this is not set. */
+    public Voicemail.Mailbox getMailbox();
+
+    public boolean hasMailbox();
+
+    /**
+     * Tells us if the voicemail message has been marked as read.
+     * <p>
+     * Always returns false if this field has not been set, i.e. if hasRead() returns false.
+     */
+    public boolean isRead();
+
+    public boolean hasRead();
+
+    /**
+     * Tells us if there is content stored at the Uri.
+     */
+    public boolean hasContent();
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailFilter.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailFilter.java
new file mode 100644
index 0000000..20abe33
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailFilter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+/**
+ * An object that can be used to apply filter on voicemail queries made through the voicemail helper
+ * interface.
+ */
+public interface VoicemailFilter {
+    /** Returns the where clause for this filter. Returns null if the filter is empty. */
+    public String getWhereClause();
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailFilterFactory.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailFilterFactory.java
new file mode 100644
index 0000000..dbb7338
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailFilterFactory.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.DATE;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.DURATION;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.NUMBER;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.PROVIDER;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.PROVIDER_DATA;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.READ_STATUS;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.STATE;
+import static com.example.android.voicemail.common.utils.DbQueryUtils.concatenateClausesWithAnd;
+import static com.example.android.voicemail.common.utils.DbQueryUtils.concatenateClausesWithOr;
+
+import com.example.android.voicemail.common.core.Voicemail.Mailbox;
+import com.example.android.voicemail.common.utils.DbQueryUtils;
+
+import android.text.TextUtils;
+
+import com.android.providers.voicemail.api.VoicemailProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Factory class to create {@link VoicemailFilter} objects for various filtering needs.
+ * <p>
+ * Factory methods like {@link #createWithMailbox(Mailbox)}, {@link #createWithReadStatus(boolean)} and
+ * {@link #createWithMatchingFields(Voicemail)} can be used to create a voicemail filter that matches the
+ * value of the specific field.
+ * <p>
+ * It it possible to combine multiple filters with OR or AND operation using the methods
+ * {@link #createWithOrOf(VoicemailFilter...)} and {@link #createWithAndOf(VoicemailFilter...)} respectively.
+ * <p>
+ * {@link #createWithWhereClause(String)} can be used to create an arbitrary filter for a specific where
+ * clause. Using this method requires the knowledge of the name of columns used in voicemail
+ * content provider database and is therefore less recommended.
+ */
+public class VoicemailFilterFactory {
+    /** Predefined filter for inbox only messages. */
+    public static final VoicemailFilter INBOX_MESSAGES_FILTER = createWithOrOf(
+            createWithMailbox(Mailbox.INBOX), createWithMailbox(Mailbox.UNDELETED));
+    /** Predefined filter for trashed messages. */
+    public static final VoicemailFilter TRASHED_MESSAGES_FILTER =
+            createWithMailbox(Mailbox.DELETED);
+
+    /**
+     * Creates a voicemail filter with the specified where clause. Use this method only if you know
+     * and want to directly use the column names of the content provider. For most of the usages one
+     * the other factory methods should be good enough.
+     */
+    public static VoicemailFilter createWithWhereClause(final String whereClause) {
+        return new VoicemailFilter() {
+          @Override
+          public String getWhereClause() {
+            return TextUtils.isEmpty(whereClause) ? null : whereClause;
+          }
+          @Override
+          public String toString() {
+              return getWhereClause();
+          }
+        };
+    }
+
+    /** Creates a filter with fields matching the ones set in the supplied voicemail object. */
+    public static VoicemailFilter createWithMatchingFields(Voicemail fieldMatch) {
+        if (fieldMatch == null) {
+            throw new IllegalArgumentException("Cannot create filter null fieldMatch");
+        }
+        return VoicemailFilterFactory.createWithWhereClause(getWhereClauseForMatchingFields(fieldMatch));
+    }
+
+    /** Creates a voicemail filter with the specified mailbox state. */
+    public static VoicemailFilter createWithMailbox(Mailbox mailbox) {
+        return createWithMatchingFields(VoicemailImpl.createEmptyBuilder().setMailbox(mailbox).build());
+    }
+
+    /** Creates a voicemail filter with the specified read status. */
+    public static VoicemailFilter createWithReadStatus(boolean isRead) {
+        return createWithMatchingFields(VoicemailImpl.createEmptyBuilder().setIsRead(isRead).build());
+    }
+
+    /** Combine multiple filters with OR clause. */
+    public static VoicemailFilter createWithAndOf(VoicemailFilter... filters) {
+        return createWithWhereClause(concatenateClausesWithAnd(getClauses(filters)));
+    }
+
+    /** Combine multiple filters with AND clause. */
+    public static VoicemailFilter createWithOrOf(VoicemailFilter... filters) {
+        return createWithWhereClause(concatenateClausesWithOr(getClauses(filters)));
+    }
+
+    private static String[] getClauses(VoicemailFilter[] filters) {
+        String[] clauses = new String[filters.length];
+        for (int i = 0; i < filters.length; ++i) {
+            clauses[i] = filters[i].getWhereClause();
+        }
+        return clauses;
+    }
+
+    private static String getWhereClauseForMatchingFields(Voicemail fieldMatch) {
+        List<String> clauses = new ArrayList<String>();
+        if (fieldMatch.hasRead()) {
+            clauses.add(getEqualityClause(READ_STATUS, fieldMatch.isRead() ? "1" : "0"));
+        }
+        if (fieldMatch.hasMailbox()) {
+            clauses.add(getEqualityClause(STATE,
+                    Integer.toString(fieldMatch.getMailbox().getValue())));
+        }
+        if (fieldMatch.hasNumber()) {
+            clauses.add(getEqualityClause(NUMBER, fieldMatch.getNumber()));
+        }
+        if (fieldMatch.hasSource()) {
+            clauses.add(getEqualityClause(PROVIDER, fieldMatch.getSource()));
+        }
+        if (fieldMatch.hasProviderData()) {
+            clauses.add(getEqualityClause(PROVIDER_DATA, fieldMatch.getProviderData()));
+        }
+        if (fieldMatch.hasDuration()) {
+            clauses.add(getEqualityClause(DURATION, Long.toString(fieldMatch.getDuration())));
+        }
+        if (fieldMatch.hasTimestampMillis()) {
+            clauses.add(getEqualityClause(DATE, Long.toString(fieldMatch.getTimestampMillis())));
+        }
+        // Empty filter.
+        if (clauses.size() == 0) {
+            return null;
+        }
+        return concatenateClausesWithAnd(clauses.toArray(new String[0]));
+    }
+
+    private static String getEqualityClause(String field, String value) {
+        return DbQueryUtils.getEqualityClause(VoicemailProvider.Tables.Voicemails.NAME, field,
+                value);
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailImpl.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailImpl.java
new file mode 100644
index 0000000..60f9069
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailImpl.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+import android.net.Uri;
+
+/**
+ * A simple immutable data object to represent a voicemail.
+ */
+public final class VoicemailImpl implements Voicemail {
+    private final Long mTimestamp;
+    private final String mNumber;
+    private final Long mId;
+    private final Long mDuration;
+    private final String mSource;
+    private final String mProviderData;
+    private final Uri mUri;
+    private final Voicemail.Mailbox mMailbox;
+    private final Boolean mIsRead;
+    private final boolean mHasContent;
+
+    // TODO: 5. We should probably consider changing "number" everywhere to "contact", given that
+    // it's not clear that these will be restricted to telephone numbers.
+
+    private VoicemailImpl(
+            Long timestamp,
+            String number,
+            Long id,
+            Long duration,
+            String source,
+            String providerData,
+            Uri uri,
+            Voicemail.Mailbox mailbox,
+            Boolean isRead,
+            boolean hasContent) {
+        mId = id;
+        mNumber = number;
+        mDuration = duration;
+        mTimestamp = timestamp;
+        mSource = source;
+        mProviderData = providerData;
+        mUri = uri;
+        mMailbox = mailbox;
+        mIsRead = isRead;
+        mHasContent = hasContent;
+    }
+
+    /**
+     * Create a {@link Builder} for a new {@link Voicemail} to be inserted.
+     * <p>
+     * The number and the timestamp are mandatory for insertion.
+     */
+    public static Builder createForInsertion(long timestamp, String number) {
+        return new Builder().setNumber(number).setTimestamp(timestamp);
+    }
+
+    /**
+     * Create a {@link Builder} for updating a {@link Voicemail}.
+     * <p>
+     * Only the id of the voicemail to be updated is mandatory.
+     */
+    public static Builder createForUpdate(long id) {
+        return new Builder().setId(id);
+    }
+
+    /**
+     * Create a {@link Builder} for a new {@link Voicemail}, such as one suitable for returning from
+     * a list of results or creating from scratch.
+     */
+    public static Builder createEmptyBuilder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder pattern for creating a {@link VoicemailImpl}.
+     * <p>
+     * All fields are optional, and can be set with the various {@code setXXX} methods.
+     */
+    public static class Builder {
+        private Long mBuilderTimestamp;
+        private String mBuilderNumber;
+        private Long mBuilderId;
+        private Long mBuilderDuration;
+        private String mBuilderSource;
+        private String mBuilderProviderData;
+        private Uri mBuilderUri;
+        private Voicemail.Mailbox mBuilderMailbox;
+        private Boolean mBuilderIsRead;
+        private boolean mBuilderHasContent;
+
+        /** You should use the correct factory method to construct a builder. */
+        private Builder() {
+        }
+
+        public Builder setNumber(String number) {
+            mBuilderNumber = number;
+            return this;
+        }
+
+        public Builder setTimestamp(long timestamp) {
+            mBuilderTimestamp = timestamp;
+            return this;
+        }
+
+        public Builder setId(long id) {
+            mBuilderId = id;
+            return this;
+        }
+
+        public Builder setDuration(long duration) {
+            mBuilderDuration = duration;
+            return this;
+        }
+
+        public Builder setSource(String source) {
+            mBuilderSource = source;
+            return this;
+        }
+
+        public Builder setProviderData(String providerData) {
+            mBuilderProviderData = providerData;
+            return this;
+        }
+
+        public Builder setUri(Uri uri) {
+            mBuilderUri = uri;
+            return this;
+        }
+
+        public Builder setMailbox(Voicemail.Mailbox mailbox) {
+            mBuilderMailbox = mailbox;
+            return this;
+        }
+
+        public Builder setIsRead(boolean isRead) {
+            mBuilderIsRead = isRead;
+            return this;
+        }
+
+        public Builder setHasContent(boolean hasContent) {
+            mBuilderHasContent = hasContent;
+            return this;
+        }
+
+        public VoicemailImpl build() {
+            return new VoicemailImpl(mBuilderTimestamp, mBuilderNumber, mBuilderId,
+                    mBuilderDuration,
+                    mBuilderSource, mBuilderProviderData, mBuilderUri, mBuilderMailbox,
+                    mBuilderIsRead,
+                    mBuilderHasContent);
+        }
+    }
+
+    @Override
+    public long getId() {
+        return hasId() ? mId : -1;
+    }
+
+    @Override
+    public boolean hasId() {
+        return mId != null;
+    }
+
+    @Override
+    public String getNumber() {
+        return mNumber;
+    }
+
+    @Override
+    public boolean hasNumber() {
+        return mNumber != null;
+    }
+
+    @Override
+    public long getTimestampMillis() {
+        return hasTimestampMillis() ? mTimestamp : 0;
+    }
+
+    @Override
+    public boolean hasTimestampMillis() {
+        return mTimestamp != null;
+    }
+
+    @Override
+    public long getDuration() {
+        return hasDuration() ? mDuration : 0;
+    }
+
+    @Override
+    public boolean hasDuration() {
+        return mDuration != null;
+    }
+
+    @Override
+    public String getSource() {
+        return mSource;
+    }
+
+    @Override
+    public boolean hasSource() {
+        return mSource != null;
+    }
+
+    @Override
+    public String getProviderData() {
+        return mProviderData;
+    }
+
+    @Override
+    public boolean hasProviderData() {
+        return mProviderData != null;
+    }
+
+    @Override
+    public Uri getUri() {
+        return mUri;
+    }
+
+    @Override
+    public boolean hasUri() {
+        return mUri != null;
+    }
+
+    @Override
+    public Mailbox getMailbox() {
+        return mMailbox;
+    }
+
+    @Override
+    public boolean hasMailbox() {
+        return mMailbox != null;
+    }
+
+    @Override
+    public boolean isRead() {
+        return hasRead() ? mIsRead : false;
+    }
+
+    @Override
+    public boolean hasRead() {
+        return mIsRead != null;
+    }
+
+    @Override
+    public boolean hasContent() {
+        return mHasContent;
+    }
+
+    @Override
+    public String toString() {
+        return "VoicemailImpl [mTimestamp=" + mTimestamp + ", mNumber=" + mNumber + ", mId=" + mId
+                + ", mDuration=" + mDuration + ", mSource=" + mSource + ", mProviderData="
+                + mProviderData + ", mUri=" + mUri + ", mMailbox=" + mMailbox + ", mIsRead="
+                + mIsRead + ", mHasContent=" + mHasContent + "]";
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailIntentUtils.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailIntentUtils.java
new file mode 100644
index 0000000..82d971a
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailIntentUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Stores and retrieves relevant bits of voicemails in an Intent.
+ */
+public class VoicemailIntentUtils {
+    /** The String used when storing provider data in intents. */
+    public static final String PROVIDER_DATA_KEY = VoicemailImpl.class.getName() + ".PROVIDER_DATA";
+
+    // Private constructor, utility class.
+    private VoicemailIntentUtils() {
+    }
+
+    /**
+     * Stores the {@link Voicemail#getProviderData()} value into an intent.
+     *
+     * @see #extractIdentifierFromIntent(Intent)
+     */
+    public static void storeIdentifierInIntent(Intent intent, Voicemail message) {
+        intent.putExtra(PROVIDER_DATA_KEY, message.getProviderData());
+    }
+
+    /**
+     * Retrieves the {@link Voicemail#getProviderData()} from an intent.
+     * <p>
+     * Returns null if the Intent contains no such identifier, or has no extras.
+     *
+     * @see #storeIdentifierInIntent(Intent, Voicemail)
+     */
+    public static String extractIdentifierFromIntent(Intent intent) {
+        Bundle extras = intent.getExtras();
+        return (extras == null ? null : extras.getString(PROVIDER_DATA_KEY));
+    }
+
+    /**
+     * Copies the extras stored by {@link #storeIdentifierInIntent(Intent, Voicemail)} between two
+     * intents.
+     */
+    public static void copyExtrasBetween(Intent from, Intent to) {
+        Bundle extras = from.getExtras();
+        if (extras.containsKey(PROVIDER_DATA_KEY)) {
+            to.putExtra(PROVIDER_DATA_KEY, extras.getString(PROVIDER_DATA_KEY));
+        }
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailPayload.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailPayload.java
new file mode 100644
index 0000000..0e088a6
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailPayload.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+/**
+ * The payload for a voicemail, usually audio data.
+ */
+public interface VoicemailPayload {
+    public String getMimeType();
+
+    public byte[] getBytes();
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailPayloadImpl.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailPayloadImpl.java
new file mode 100644
index 0000000..3694b56
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailPayloadImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+/**
+ * Concrete implementation of {@link VoicemailPayload} interface.
+ */
+public class VoicemailPayloadImpl implements VoicemailPayload {
+    private final String mMimeType;
+    private final byte[] mBytes;
+
+    public VoicemailPayloadImpl(String mimeType, byte[] bytes) {
+        mMimeType = mimeType;
+        mBytes = bytes.clone();
+    }
+
+    @Override
+    public byte[] getBytes() {
+        return mBytes.clone();
+    }
+
+    @Override
+    public String getMimeType() {
+        return mMimeType;
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailProviderHelper.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailProviderHelper.java
new file mode 100644
index 0000000..72610a5
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailProviderHelper.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+import android.net.Uri;
+
+import com.android.providers.voicemail.api.VoicemailProvider;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Provides a simple interface to manipulate voicemails within the voicemail content provider.
+ * <p>
+ * Methods on this interface throw checked exceptions only where the corresponding underlying
+ * methods perform an operation that itself requires a checked exception. In all other cases a
+ * {@link RuntimeException} will be thrown here.
+ * <p>
+ * These methods are blocking, and will return control to the caller only when the operation
+ * completes. You should not call any of these methods from your main ui thread, as this may result
+ * in your application becoming unresponsive.
+ */
+public interface VoicemailProviderHelper {
+
+    /** Sort order to return results by. */
+    public enum SortOrder {
+        ASCENDING,
+        DESCENDING,
+        /** Default sort order returned by DB. (Typically Ascending, but no guarantees made). */
+        DEFAULT
+    }
+
+    /**
+     * Clears all voicemails accessible to this voicemail content provider.
+     *
+     * @return the number of voicemails deleted
+     */
+    public int deleteAll();
+
+    /**
+     * Inserts a new voicemail into the voicemail content provider.
+     *
+     * @param voicemail data to be inserted
+     * @return {@link Uri} of the newly inserted {@link Voicemail}
+     * @throws IllegalArgumentException if any of the following are true:
+     *         <ul>
+     *         <li>your voicemail is missing a timestamp</li>
+     *         <li>your voiceamil is missing a number</li>
+     *         <li>your voicemail is missing the provider id field</li>
+     *         <li>voicemail has an id (which would indicate that it has already been inserted)
+     *         </li>
+     *         </ul>
+     */
+    public Uri insert(Voicemail voicemail);
+
+    /**
+     * Returns the {@link Voicemail} whose provider data matches the given value.
+     * <p>
+     * It is expected that there be one such voicemail. Returns null if no such voicemail exists,
+     * and returns one chosen arbitrarily if more than one exists.
+     */
+    public Voicemail findVoicemailByProviderData(String providerData);
+
+    /**
+     * Returns the {@link Voicemail} corresponding to a given Uri. The uri must correspond to a
+     * unique voicemail record.
+     * <p>
+     * Returns null if no voicemail was found that exactly matched the given uri.
+     */
+    public Voicemail findVoicemailByUri(Uri uri);
+
+    /**
+     * Updates an existing voicemail in the content provider.
+     * <p>
+     * Note that <b>only the fields that are set</b> on the {@link Voicemail} that you provide will
+     * be used to perform the update. The remaining fields will be left unmodified. To mark a
+     * voicemail as read, create a new {@link Voicemail} that is marked as read, and call update.
+     *
+     * @throws IllegalArgumentException if you provide a {@link Voicemail} that already has a Uri
+     *             set, because we don't support altering the Uri of a voicemail, and this most
+     *             likely implies that you're using this api incorrectly
+     * @return the number of rows that were updated
+     */
+    public int update(Uri uri, Voicemail voicemail);
+
+    /**
+     * Get the OutputStream to write the voicemail content with the given mime type.
+     * <p>
+     * <b>Remember to close the OutputStream after you're done writing.</b>
+     *
+     * @throws IOException if there is a problem creating the file or no voicemail is found matching
+     *             the given Uri
+     */
+    public OutputStream setVoicemailContent(Uri voicemailUri, String mimeType) throws IOException;
+
+    /**
+     * Fetch all the voicemails accessible to this voicemail content provider.
+     *
+     * @return the list of voicemails, no guarantee is made about the ordering
+     */
+    public List<Voicemail> getAllVoicemails();
+
+    /**
+     * Same as {@link #getAllVoicemails()} but also sorts them by the requested column and allows to
+     * set a filter.
+     *
+     * @param filter The filter to apply while retrieving voicemails.
+     * @param sortColumn The column to sort by. Must be one of the values defined in
+     *            {@link VoicemailProvider.Tables.Voicemails.Columns}.
+     * @param sortOrder Order to sort by
+     * @return the list of voicemails, sorted by the requested DB column in specified sort order.
+     */
+    public List<Voicemail> getAllVoicemails(VoicemailFilter filter,
+            String sortColumn, SortOrder sortOrder);
+
+    /**
+     * Returns the Uri for the voicemail with the specified message Id.
+     */
+    public Uri getUriForVoicemailWithId(long id);
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailProviderHelpers.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailProviderHelpers.java
new file mode 100644
index 0000000..eda9b54
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/core/VoicemailProviderHelpers.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.core;
+
+import static com.android.providers.voicemail.api.VoicemailProvider.CONTENT_URI_PROVIDER_ID_QUERY;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.DATA_MIME_TYPE;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.DATE;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.DURATION;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.NUMBER;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.PROVIDER;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.PROVIDER_DATA;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.READ_STATUS;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns.STATE;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns._DATA_FILE_EXISTS;
+import static com.android.providers.voicemail.api.VoicemailProvider.Tables.Voicemails.Columns._ID;
+
+import com.example.android.voicemail.common.logging.Logger;
+import com.example.android.voicemail.common.utils.CloseUtils;
+import com.example.android.voicemail.common.utils.DbQueryUtils;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import com.android.providers.voicemail.api.VoicemailProvider;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the {@link VoicemailProviderHelper} interface.
+ */
+public final class VoicemailProviderHelpers implements VoicemailProviderHelper {
+    private static final Logger logger = Logger.getLogger(VoicemailProviderHelpers.class);
+
+    /** Full projection on the voicemail table, giving us all the columns. */
+    private static final String[] FULL_PROJECTION = new String[] {
+            _ID, _DATA_FILE_EXISTS, NUMBER, DURATION, DATE, PROVIDER, PROVIDER_DATA, READ_STATUS,
+            STATE
+    };
+
+    private final ContentResolver mContentResolver;
+    private final Uri mBaseUri;
+
+    /**
+     * Creates an instance of {@link VoicemailProviderHelpers} that wraps the supplied content
+     * provider.
+     *
+     * @param contentResolver the ContentResolver used for opening the output stream to read and
+     *            write to the file
+     */
+    private VoicemailProviderHelpers(Uri baseUri, ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+        mBaseUri = baseUri;
+    }
+
+    /**
+     * Constructs a VoicemailProviderHelper with full access to all voicemails.
+     * <p>
+     * Requires the manifest permissions
+     * <code>com.android.providers.voicemail.permission.READ_WRITE_ALL_VOICEMAIL</code> and
+     * <code>com.android.providers.voicemail.permission.READ_WRITE_OWN_VOICEMAIL</code>.
+     */
+    public static VoicemailProviderHelper createFullVoicemailProvider(Context context) {
+        return new VoicemailProviderHelpers(VoicemailProvider.CONTENT_URI,
+                context.getContentResolver());
+    }
+
+    /**
+     * Constructs a VoicemailProviderHelper with limited access to voicemails created by this
+     * source.
+     * <p>
+     * Requires the manifest permission
+     * <code>com.android.providers.voicemail.permission.READ_WRITE_OWN_VOICEMAIL</code>.
+     */
+    public static VoicemailProviderHelper createPackageScopedVoicemailProvider(Context context) {
+        Uri providerUri = Uri.withAppendedPath(VoicemailProvider.CONTENT_URI_PROVIDER_QUERY,
+                context.getPackageName());
+        return new VoicemailProviderHelpers(providerUri, context.getContentResolver());
+    }
+
+    @Override
+    public Uri insert(Voicemail voicemail) {
+        check(!voicemail.hasId(), "Inserted voicemails must not have an id", voicemail);
+        check(voicemail.hasTimestampMillis(), "Inserted voicemails must have a timestamp",
+                voicemail);
+        check(voicemail.hasNumber(), "Inserted voicemails must have a number", voicemail);
+        logger.d(String.format("Inserting new voicemail: %s", voicemail));
+        ContentValues contentValues = getContentValues(voicemail);
+        return mContentResolver.insert(mBaseUri, contentValues);
+    }
+
+    @Override
+    public int update(Uri uri, Voicemail voicemail) {
+        check(!voicemail.hasUri(), "Can't update the Uri of a voicemail", voicemail);
+        logger.d("Updating voicemail: " + voicemail + " for uri: " + uri);
+        ContentValues values = getContentValues(voicemail);
+        return mContentResolver.update(uri, values, null, null);
+    }
+
+    @Override
+    public OutputStream setVoicemailContent(Uri voicemailUri, String mimeType) throws IOException {
+        ContentValues values = new ContentValues();
+        values.put(DATA_MIME_TYPE, mimeType);
+        int updatedCount = mContentResolver.update(voicemailUri, values, null, null);
+        if (updatedCount != 1) {
+            throw new IOException("Updating voicemail should have updated 1 row, was: "
+                    + updatedCount);
+        }
+        logger.d(String.format("Writing new voicemail content: %s", voicemailUri));
+        return mContentResolver.openOutputStream(voicemailUri);
+    }
+
+    @Override
+    public Voicemail findVoicemailByProviderData(String providerData) {
+        Cursor cursor = null;
+        try {
+            cursor = mContentResolver.query(mBaseUri, FULL_PROJECTION,
+                    DbQueryUtils.getEqualityClause(
+                            VoicemailProvider.Tables.Voicemails.NAME, PROVIDER_DATA, providerData),
+                    null, null);
+            if (cursor.getCount() != 1) {
+                logger.w("Expected 1 voicemail matching providerData " + providerData + ", got " +
+                        cursor.getCount());
+                return null;
+            }
+            cursor.moveToFirst();
+            return getVoicemailFromCursor(cursor);
+        } finally {
+            CloseUtils.closeQuietly(cursor);
+        }
+    }
+
+    @Override
+    public Voicemail findVoicemailByUri(Uri uri) {
+        Cursor cursor = null;
+        try {
+            cursor = mContentResolver.query(uri, FULL_PROJECTION, null, null, null);
+            if (cursor.getCount() != 1) {
+                logger.w("Expected 1 voicemail matching uri " + uri + ", got " + cursor.getCount());
+                return null;
+            }
+            cursor.moveToFirst();
+            Voicemail voicemail = getVoicemailFromCursor(cursor);
+            // Make sure this is an exact match.
+            if (voicemail.getUri().equals(uri)) {
+                return voicemail;
+            } else {
+                logger.w("Queried uri: " + uri + " do not represent a unique voicemail record.");
+                return null;
+            }
+        } finally {
+            CloseUtils.closeQuietly(cursor);
+        }
+    }
+
+    @Override
+    public Uri getUriForVoicemailWithId(long id) {
+        return ContentUris.withAppendedId(mBaseUri, id);
+    }
+
+    /**
+     * Checks that an assertion is true.
+     *
+     * @throws IllegalArgumentException if the assertion is false, along with a suitable message
+     *             including a toString() representation of the voicemail
+     */
+    private void check(boolean assertion, String message, Voicemail voicemail) {
+        if (!assertion) {
+            throw new IllegalArgumentException(message + ": " + voicemail);
+        }
+    }
+
+    @Override
+    public int deleteAll() {
+        logger.i(String.format("Deleting all voicemails"));
+        return mContentResolver.delete(mBaseUri, "", new String[0]);
+    }
+
+    @Override
+    public List<Voicemail> getAllVoicemails() {
+        return getAllVoicemails(null, null, SortOrder.DEFAULT);
+    }
+
+    @Override
+    public List<Voicemail> getAllVoicemails(VoicemailFilter filter,
+            String sortColumn, SortOrder sortOrder) {
+        logger.i(String.format("Fetching all voicemails"));
+        Cursor cursor = null;
+        try {
+            cursor = mContentResolver.query(mBaseUri, FULL_PROJECTION,
+                    filter != null ? filter.getWhereClause() : null,
+                    null, getSortBy(sortColumn, sortOrder));
+            List<Voicemail> results = new ArrayList<Voicemail>(cursor.getCount());
+            while (cursor.moveToNext()) {
+                // A performance optimisation is possible here.
+                // The helper method extracts the column indices once every time it is called,
+                // whilst
+                // we could extract them all up front (without the benefit of the re-use of the
+                // helper
+                // method code).
+                // At the moment I'm pretty sure the benefits outweigh the costs, so leaving as-is.
+                results.add(getVoicemailFromCursor(cursor));
+            }
+            return results;
+        } finally {
+            CloseUtils.closeQuietly(cursor);
+        }
+    }
+
+    private String getSortBy(String column, SortOrder sortOrder) {
+        if (column == null) {
+            return null;
+        }
+        switch (sortOrder) {
+            case ASCENDING:
+                return column + " ASC";
+            case DESCENDING:
+                return column + " DESC";
+            case DEFAULT:
+                return column;
+        }
+        // Should never reach here.
+        return null;
+    }
+
+    private VoicemailImpl getVoicemailFromCursor(Cursor cursor) {
+        long id = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
+        String provider = cursor.getString(cursor.getColumnIndexOrThrow(PROVIDER));
+        Uri voicemailUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(CONTENT_URI_PROVIDER_ID_QUERY, provider), id);
+        VoicemailImpl voicemail = VoicemailImpl
+                .createEmptyBuilder()
+                .setTimestamp(cursor.getLong(cursor.getColumnIndexOrThrow(DATE)))
+                .setNumber(cursor.getString(cursor.getColumnIndexOrThrow(NUMBER)))
+                .setId(id)
+                .setDuration(cursor.getLong(cursor.getColumnIndexOrThrow(DURATION)))
+                .setSource(provider)
+                .setProviderData(cursor.getString(cursor.getColumnIndexOrThrow(PROVIDER_DATA)))
+                .setUri(voicemailUri)
+                .setHasContent(cursor.getInt(cursor.getColumnIndexOrThrow(_DATA_FILE_EXISTS)) == 1)
+                .setIsRead(cursor.getInt(cursor.getColumnIndexOrThrow(READ_STATUS)) == 1)
+                .setMailbox(
+                        mapValueToMailBoxEnum(cursor.getInt(cursor.getColumnIndexOrThrow(STATE))))
+                .build();
+        return voicemail;
+    }
+
+    private Voicemail.Mailbox mapValueToMailBoxEnum(int value) {
+        for (Voicemail.Mailbox mailbox : Voicemail.Mailbox.values()) {
+            if (mailbox.getValue() == value) {
+                return mailbox;
+            }
+        }
+        throw new IllegalArgumentException("Value: " + value + " not valid for Voicemail.Mailbox.");
+    }
+
+    /**
+     * Maps structured {@link Voicemail} to {@link ContentValues} understood by content provider.
+     */
+    private ContentValues getContentValues(Voicemail voicemail) {
+        ContentValues contentValues = new ContentValues();
+        if (voicemail.hasTimestampMillis()) {
+            contentValues.put(DATE, String.valueOf(voicemail.getTimestampMillis()));
+        }
+        if (voicemail.hasNumber()) {
+            contentValues.put(NUMBER, voicemail.getNumber());
+        }
+        if (voicemail.hasDuration()) {
+            contentValues.put(DURATION, String.valueOf(voicemail.getDuration()));
+        }
+        if (voicemail.hasSource()) {
+            contentValues.put(PROVIDER, voicemail.getSource());
+        }
+        if (voicemail.hasProviderData()) {
+            contentValues.put(PROVIDER_DATA, voicemail.getProviderData());
+        }
+        if (voicemail.hasRead()) {
+            contentValues.put(READ_STATUS, voicemail.isRead() ? 1 : 0);
+        }
+        if (voicemail.hasMailbox()) {
+            contentValues.put(STATE, voicemail.getMailbox().getValue());
+        }
+        return contentValues;
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/inject/InjectView.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/inject/InjectView.java
new file mode 100644
index 0000000..59e6213
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/inject/InjectView.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.inject;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation to mark the fields of your Activity as being injectable.
+ * <p>
+ * See the {@link Injector} class for more details of how this operates.
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface InjectView {
+    /**
+     * The resource id of the View to find and inject.
+     */
+    public int value();
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/inject/Injector.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/inject/Injector.java
new file mode 100644
index 0000000..d9a1078
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/inject/Injector.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.inject;
+
+import android.app.Activity;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+
+/**
+ * Very lightweight form of injection, inspired by RoboGuice, for injecting common ui elements.
+ * <p>
+ * Usage is very simple. In your Activity, define some fields as follows:
+ *
+ * <pre class="code">
+ * &#064;InjectView(R.id.fetch_button)
+ * private Button mFetchButton;
+ * &#064;InjectView(R.id.submit_button)
+ * private Button mSubmitButton;
+ * &#064;InjectView(R.id.main_view)
+ * private TextView mTextView;
+ * </pre>
+ * <p>
+ * Then, inside your Activity's onCreate() method, perform the injection like this:
+ *
+ * <pre class="code">
+ * setContentView(R.layout.main_layout);
+ * Injector.get(this).inject();
+ * </pre>
+ * <p>
+ * See the {@link #inject()} method for full details of how it works. Note that the fields are
+ * fetched and assigned at the time you call {@link #inject()}, consequently you should not do this
+ * until after you've called the setContentView() method.
+ */
+public final class Injector {
+    private final Activity mActivity;
+
+    private Injector(Activity activity) {
+        mActivity = activity;
+    }
+
+    /**
+     * Gets an {@link Injector} capable of injecting fields for the given Activity.
+     */
+    public static Injector get(Activity activity) {
+        return new Injector(activity);
+    }
+
+    /**
+     * Injects all fields that are marked with the {@link InjectView} annotation.
+     * <p>
+     * For each field marked with the InjectView annotation, a call to
+     * {@link Activity#findViewById(int)} will be made, passing in the resource id stored in the
+     * value() method of the InjectView annotation as the int parameter, and the result of this call
+     * will be assigned to the field.
+     *
+     * @throws IllegalStateException if injection fails, common causes being that you have used an
+     *             invalid id value, or you haven't called setContentView() on your Activity.
+     */
+    public void inject() {
+        for (Field field : mActivity.getClass().getDeclaredFields()) {
+            for (Annotation annotation : field.getAnnotations()) {
+                if (annotation.annotationType().equals(InjectView.class)) {
+                    try {
+                        Class<?> fieldType = field.getType();
+                        int idValue = InjectView.class.cast(annotation).value();
+                        field.setAccessible(true);
+                        Object injectedValue = fieldType.cast(mActivity.findViewById(idValue));
+                        if (injectedValue == null) {
+                            throw new IllegalStateException("findViewById(" + idValue
+                                    + ") gave null for " +
+                                    field + ", can't inject");
+                        }
+                        field.set(mActivity, injectedValue);
+                        field.setAccessible(false);
+                    } catch (IllegalAccessException e) {
+                        throw new IllegalStateException(e);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/logging/Logger.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/logging/Logger.java
new file mode 100644
index 0000000..2683fa1
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/logging/Logger.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.logging;
+
+import android.util.Log;
+
+/**
+ * Simplifies usage of Android logging class {@link Log} by abstracting the TAG field that is
+ * required to be passed to every logging method. Also, allows automatic insertion of the owner
+ * class name prefix to log outputs for better debugging.
+ * <p>
+ * Use {@link #getLogger(Class)} to create an instance of Logger that automatically inserts the
+ * class name as a prefix to each log output. If you do not want the class name to be prefixed to
+ * log output then use {@link #getLogger()} to create the instance of Logger.
+ */
+public class Logger {
+    private static final String APP_TAG = "VoicemailSample";
+
+    /**
+     * Use this method if you want your class name to be prefixed to each log output.
+     */
+    public static Logger getLogger(Class<?> classZ) {
+        return new Logger(classZ.getSimpleName() + ": ");
+    }
+
+    /**
+     * Use this factory method if you DO NOT want your class name to be prefixed into the log
+     * output.
+     */
+    public static Logger getLogger() {
+        return new Logger();
+    }
+
+    private final String mLogPrefix;
+
+    /** No custom log prefix used. */
+    private Logger() {
+        mLogPrefix = null;
+    }
+
+    /** Use the supplied custom prefix in log output. */
+    private Logger(String logPrefix) {
+        mLogPrefix = logPrefix;
+    }
+
+    private String getMsg(String msg) {
+        if (mLogPrefix != null) {
+            return mLogPrefix + msg;
+        } else {
+            return msg;
+        }
+    }
+
+    public void i(String msg) {
+        Log.i(APP_TAG, getMsg(msg));
+    }
+
+    public void i(String msg, Throwable t) {
+        Log.i(APP_TAG, getMsg(msg), t);
+    }
+
+    public void d(String msg) {
+        Log.d(APP_TAG, getMsg(msg));
+    }
+
+    public void d(String msg, Throwable t) {
+        Log.d(APP_TAG, getMsg(msg), t);
+    }
+
+    public void w(String msg) {
+        Log.w(APP_TAG, getMsg(msg));
+    }
+
+    public void w(String msg, Throwable t) {
+        Log.w(APP_TAG, getMsg(msg), t);
+    }
+
+    public void e(String msg) {
+        Log.e(APP_TAG, getMsg(msg));
+    }
+
+    public void e(String msg, Throwable t) {
+        Log.e(APP_TAG, getMsg(msg), t);
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/ui/DialogHelper.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/ui/DialogHelper.java
new file mode 100644
index 0000000..95df4b3
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/ui/DialogHelper.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.ui;
+
+/**
+ * Show common Dialogs.
+ * <p>
+ * Contains methods to show common types of Dialog. This is done both for ease of code re-use and to
+ * improve testability. See the implementation {@link DialogHelperImpl} for details.
+ */
+public interface DialogHelper {
+    public void showErrorMessageDialog(int titleId, Exception exception);
+
+    public void showErrorMessageDialog(String title, Exception exception);
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/ui/DialogHelperImpl.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/ui/DialogHelperImpl.java
new file mode 100644
index 0000000..cf067ed
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/ui/DialogHelperImpl.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.ui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Bundle;
+
+/**
+ * Uses an {@link Activity} to show Dialogs.
+ * <p>
+ * Instantiate this class inside your Activity.
+ *
+ * <pre class="code">
+ * private final DialogHelperImpl mDialogHelper = new DialogHelperImpl(this);
+ * </pre>
+ * <p>
+ * Override your Activity's onCreateDialog(int, Bundle) method, as follows:
+ *
+ * <pre class="code">
+ * &#064;Override
+ * protected Dialog onCreateDialog(int id, Bundle bundle) {
+ *     return mDialogHelper.handleOnCreateDialog(id, bundle);
+ * }
+ * </pre>
+ * <p>
+ * Now you can pass mDialogHelper around as a {@link DialogHelper} interface, and code that wants to
+ * show a Dialog can call a method like this:
+ *
+ * <pre class="code">
+ * mDialogHelper.showErrorMessageDialog(&quot;An exception occurred!&quot;, e);
+ * </pre>
+ * <p>
+ * If you want more flexibility, and want to mix this implementation with your own dialogs, then you
+ * should do something like this in your Activity:
+ *
+ * <pre class="code">
+ * &#064;Override
+ * protected Dialog onCreateDialog(int id, Bundle bundle) {
+ *     switch (id) {
+ *         case ID_MY_OTHER_DIALOG:
+ *             return new AlertDialog.Builder(this)
+ *                     .setTitle(&quot;something&quot;)
+ *                     .create();
+ *         default:
+ *             return mDialogHelper.handleOnCreateDialog(id, bundle);
+ *     }
+ * }
+ * </pre>
+ *
+ * Just be careful that you don't pick any IDs that conflict with those used by this class (which
+ * are documented in the public static final fields).
+ */
+public class DialogHelperImpl implements DialogHelper {
+    public static final int DIALOG_ID_EXCEPTION = 88953588;
+
+    private static final String KEY_EXCEPTION = "exception";
+    private static final String KEY_TITLE = "title";
+
+    private final Activity mActivity;
+
+    public DialogHelperImpl(Activity activity) {
+        mActivity = activity;
+    }
+
+    @Override
+    public void showErrorMessageDialog(int titleId, Exception exception) {
+        showErrorMessageDialog(mActivity.getString(titleId), exception);
+    }
+
+    @Override
+    public void showErrorMessageDialog(String title, Exception exception) {
+        Bundle bundle = new Bundle();
+        bundle.putString(KEY_TITLE, title);
+        bundle.putSerializable(KEY_EXCEPTION, exception);
+        mActivity.showDialog(DIALOG_ID_EXCEPTION, bundle);
+    }
+
+    /**
+     * You should call this method from your Activity's onCreateDialog(int, Bundle) method.
+     */
+    public Dialog handleOnCreateDialog(int id, Bundle args) {
+        if (id == DIALOG_ID_EXCEPTION) {
+            Exception exception = (Exception) args.getSerializable(KEY_EXCEPTION);
+            String title = args.getString(KEY_TITLE);
+            return new AlertDialog.Builder(mActivity)
+                    .setTitle(title)
+                    .setMessage(convertExceptionToErrorMessage(exception))
+                    .setCancelable(true)
+                    .create();
+        }
+        return null;
+    }
+
+    private String convertExceptionToErrorMessage(Exception exception) {
+        StringBuilder sb = new StringBuilder().append(exception.getClass().getSimpleName());
+        if (exception.getMessage() != null) {
+            sb.append("\n");
+            sb.append(exception.getMessage());
+        }
+        return sb.toString();
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/utils/CloseUtils.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/utils/CloseUtils.java
new file mode 100644
index 0000000..56d8e93
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/utils/CloseUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.utils;
+
+import android.database.Cursor;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Utility methods for closing io streams and database cursors.
+ */
+public class CloseUtils {
+    private CloseUtils() {
+    }
+
+    /**
+     * If the argument is non-null, close the Closeable ignoring any {@link IOException}.
+     */
+    public static void closeQuietly(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (IOException e) {
+                // Ignore.
+            }
+        }
+    }
+
+    /** If the argument is non-null, close the cursor. */
+    public static void closeQuietly(Cursor cursor) {
+        if (cursor != null) {
+            cursor.close();
+        }
+    }
+}
diff --git a/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/utils/DbQueryUtils.java b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/utils/DbQueryUtils.java
new file mode 100644
index 0000000..1805634
--- /dev/null
+++ b/samples/VoicemailProviderDemo/src/com/example/android/voicemail/common/utils/DbQueryUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 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.voicemail.common.utils;
+
+import android.database.DatabaseUtils;
+import android.text.TextUtils;
+
+/**
+ * Static methods for helping us build database query selection strings.
+ */
+public class DbQueryUtils {
+    // Static class with helper methods, so private constructor.
+    private DbQueryUtils() {
+    }
+
+    /** Returns a WHERE clause assert equality of a field to a value. */
+    public static String getEqualityClause(String table, String field, String value) {
+        StringBuilder clause = new StringBuilder();
+        clause.append(table);
+        clause.append(".");
+        clause.append(field);
+        clause.append(" = ");
+        DatabaseUtils.appendEscapedSQLString(clause, value);
+        return clause.toString();
+    }
+
+    /** Concatenates any number of clauses using "AND". */
+    // TODO: 0. It worries me that I can change the following "AND" to "OR" and the provider tests
+    // all pass. I can also remove the braces, and the tests all pass.
+    public static String concatenateClausesWithAnd(String... clauses) {
+        return concatenateClausesWithOperation("AND", clauses);
+    }
+
+    /** Concatenates any number of clauses using "OR". */
+    public static String concatenateClausesWithOr(String... clauses) {
+        return concatenateClausesWithOperation("OR", clauses);
+    }
+
+    /** Concatenates any number of clauses using the specified operation. */
+    public static String concatenateClausesWithOperation(String operation, String... clauses) {
+        // Nothing to concatenate.
+        if (clauses.length == 1) {
+            return clauses[0];
+        }
+
+        StringBuilder builder = new StringBuilder();
+
+        for (String clause : clauses) {
+            if (!TextUtils.isEmpty(clause)) {
+                if (builder.length() > 0) {
+                    builder.append(" ").append(operation).append(" ");
+                }
+                builder.append("(");
+                builder.append(clause);
+                builder.append(")");
+            }
+        }
+        return builder.toString();
+    }
+}