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
This commit is contained in:
Debashish Chatterjee
2011-05-16 16:52:17 +01:00
parent ad41ab204d
commit 8d6d2581ff
29 changed files with 2382 additions and 0 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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 + "]";
}
}

View File

@@ -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));
}
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}