From 8d6d2581ff11ec169cd84a6610557b9afdf41e06 Mon Sep 17 00:00:00 2001
From: Debashish Chatterjee
+This is a simple sample application that demonstrates how to use voicemail
+content provider APIs to insert new voicemail records.
+
+The application includes
+
+
+
+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.
+
+Following interfaces are of particular interest:
+AddVoicemailActivity
+ ,
+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.
+
+
+
+VoicemailProviderHelper
+ and its implementation in
+
+ VoicemailProviderHelpers
+ .
+ This interface provides a good demonstration of various fields exposed by voicemail
+ content provider and their usage.
+ Voicemail
+ and its implementation in
+
+ VoicemailImpl .
+ This interface provides a structured view of most the important fields in
+ voicemail content provider.
+
+ * 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. + *
+ * 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 000000000..2a6f8fed0
--- /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
+ * The presence of a field is indicated by a corresponding 'has' method.
+ */
+public interface Voicemail {
+ /**
+ * Which mailbox the message is sitting in.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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 000000000..20abe33cd
--- /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 000000000..dbb73385c
--- /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.
+ *
+ * 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.
+ *
+ * It it possible to combine multiple filters with OR or AND operation using the methods
+ * {@link #createWithOrOf(VoicemailFilter...)} and {@link #createWithAndOf(VoicemailFilter...)} respectively.
+ *
+ * {@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
+ * 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}.
+ *
+ * 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}.
+ *
+ * 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 000000000..82d971a15
--- /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.
+ *
+ * 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 000000000..0e088a693
--- /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 000000000..3694b569b
--- /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 000000000..72610a58c
--- /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.
+ *
+ * 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.
+ *
+ * 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:
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Note that only the fields that are set 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.
+ *
+ * Remember to close the OutputStream after you're done writing.
+ *
+ * @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
+ * Requires the manifest permissions
+ *
+ * Requires the manifest permission
+ *
+ * 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 000000000..d9a10788c
--- /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.
+ *
+ * Usage is very simple. In your Activity, define some fields as follows:
+ *
+ *
+ * Then, inside your Activity's onCreate() method, perform the injection like this:
+ *
+ *
+ * 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.
+ *
+ * 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 000000000..2683fa1d1
--- /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.
+ *
+ * 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 000000000..95df4b3a5
--- /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.
+ *
+ * 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 000000000..cf067ed02
--- /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.
+ *
+ * Instantiate this class inside your Activity.
+ *
+ *
+ * Override your Activity's onCreateDialog(int, Bundle) method, as follows:
+ *
+ *
+ * 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:
+ *
+ *
+ * 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:
+ *
+ *
+ *
+ */
+ public Uri insert(Voicemail voicemail);
+
+ /**
+ * Returns the {@link Voicemail} whose provider data matches the given value.
+ * com.android.providers.voicemail.permission.READ_WRITE_ALL_VOICEMAIL and
+ * com.android.providers.voicemail.permission.READ_WRITE_OWN_VOICEMAIL.
+ */
+ 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.
+ * com.android.providers.voicemail.permission.READ_WRITE_OWN_VOICEMAIL.
+ */
+ 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
+ * @InjectView(R.id.fetch_button)
+ * private Button mFetchButton;
+ * @InjectView(R.id.submit_button)
+ * private Button mSubmitButton;
+ * @InjectView(R.id.main_view)
+ * private TextView mTextView;
+ *
+ *
+ * setContentView(R.layout.main_layout);
+ * Injector.get(this).inject();
+ *
+ *
+ * private final DialogHelperImpl mDialogHelper = new DialogHelperImpl(this);
+ *
+ *
+ * @Override
+ * protected Dialog onCreateDialog(int id, Bundle bundle) {
+ * return mDialogHelper.handleOnCreateDialog(id, bundle);
+ * }
+ *
+ *
+ * mDialogHelper.showErrorMessageDialog("An exception occurred!", e);
+ *
+ *
+ * @Override
+ * protected Dialog onCreateDialog(int id, Bundle bundle) {
+ * switch (id) {
+ * case ID_MY_OTHER_DIALOG:
+ * return new AlertDialog.Builder(this)
+ * .setTitle("something")
+ * .create();
+ * default:
+ * return mDialogHelper.handleOnCreateDialog(id, bundle);
+ * }
+ * }
+ *
+ *
+ * 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 000000000..56d8e939a
--- /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 000000000..180563455
--- /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();
+ }
+}