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

@@ -180,6 +180,7 @@ development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacTo
development/samples/USB/MissileLauncher samples/${PLATFORM_NAME}/USB/MissileLauncher development/samples/USB/MissileLauncher samples/${PLATFORM_NAME}/USB/MissileLauncher
development/samples/USB/AdbTest samples/${PLATFORM_NAME}/USB/AdbTest development/samples/USB/AdbTest samples/${PLATFORM_NAME}/USB/AdbTest
development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService
development/samples/VoicemailProviderDemo samples/${PLATFORM_NAME}/VoicemailProviderDemo
development/samples/WeatherListWidget samples/${PLATFORM_NAME}/WeatherListWidget development/samples/WeatherListWidget samples/${PLATFORM_NAME}/WeatherListWidget
development/apps/WidgetPreview samples/${PLATFORM_NAME}/WidgetPreview development/apps/WidgetPreview samples/${PLATFORM_NAME}/WidgetPreview
development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary

View File

@@ -0,0 +1,32 @@
#
# Copyright (C) 2011 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := samples
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := VoicemailProviderDemo
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
# Use the following include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Declare the contents of this Android application. The namespace
attribute brings in the Android platform namespace, and the package
supplies a unique name for the application. When writing your
own application, the package name must be changed from "com.example.*"
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1" android:versionName="1.0" package="com.example.android.voicemail">
<uses-sdk android:minSdkVersion="9" />
<uses-sdk android:targetSdkVersion="9" />
<uses-permission android:name="com.android.providers.voicemail.permission.READ_WRITE_OWN_VOICEMAIL" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".AddVoicemailActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,190 @@
Copyright (c) 2011, The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,47 @@
<p>
This is a simple sample application that demonstrates how to use voicemail
content provider APIs to insert new voicemail records.
</p>
<p>
The application includes
<a href="src/com/example/android/voicemail/AddVoicemailActivity.html">
<code>AddVoicemailActivity</code>
</a>,
an activity that lets the user enter voicemail details and record voicemail audio,
which can then be stored with the voicemail content provider by tapping the "Send"
button.
<p>
<img alt="Add voicemail" src="../images/VoicemailProviderDemo.png"
width=250px/>
</p>
<p>
In the real world, a similar application could download voicemails from a
remote voicemail server and store them locally with the voicemail content
provider. The platform would then take care of notification and rendering of the
voicemails.
</p>
<p>
Following interfaces are of particular interest:
<ul>
<li>
<a href="src/com/example/android/voicemail/common/core/VoicemailProviderHelper.html">
<code>VoicemailProviderHelper</code>
</a> and its implementation in
<a href="src/com/example/android/voicemail/common/core/VoicemailProviderHelpers.html">
<code>VoicemailProviderHelpers</code>
</a>.
This interface provides a good demonstration of various fields exposed by voicemail
content provider and their usage.
</li>
<li>
<a href="src/com/example/android/voicemail/common/core/Voicemail.html">
<code>Voicemail</code>
</a> and its implementation in
<a href="src/com/example/android/voicemail/common/core/VoicemailImpl.html">
<code>VoicemailImpl</code> </a>.
This interface provides a structured view of most the important fields in
voicemail content provider.
</li>
</ul>
</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/add_voicemail"/>
<EditText android:id = "@+id/sender_number"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/sender_number"
android:inputType="phone"/>
<EditText android:id = "@+id/time"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/time"/>
<EditText android:id = "@+id/duration"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/duration"
android:inputType="number"/>
<EditText android:id = "@+id/mime_type"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/mime_type"/>
<EditText android:id = "@+id/provider_package"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<!-- Recording button -->
<Button android:id = "@+id/start_recording_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/record_voice"/>
<!-- Save button -->
<Button android:id = "@+id/save_btn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/send"/>
</LinearLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="app_name">VoicemaiProviderDemo</string>
<!-- AddVoicemailActivity -->
<string name="add_voicemail">Add voicemail</string>
<string name="sender_number">Sender phone number</string>
<string name="time">Time in DD/MM/YYYY hh:mm</string>
<string name="duration">Duration in seconds</string>
<string name="mime_type">MIME type</string>
<string name="voicemail_store_error">Error in storing voicemail!</string>
<string name="send">Send</string>
<string name="record_voice">Record voice</string>
</resources>

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.providers.voicemail.api;
import android.content.Intent;
import android.net.Uri;
/**
* Defines the constants needed to access and interact with the voicemail content provider.
*/
public class VoicemailProvider {
/** The authority used by the voicemail provider. */
public static final String AUTHORITY =
"com.android.providers.voicemail";
/** The main URI exposed by the service. */
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/voicemail");
/** The URI to fetch an individual voicemail. */
public static final Uri CONTENT_URI_ID_QUERY =
Uri.parse("content://" + AUTHORITY + "/voicemail/");
/** The URI to fetch all voicemails from a given provider. */
public static final Uri CONTENT_URI_PROVIDER_QUERY =
Uri.parse("content://" + AUTHORITY + "/voicemail/provider/");
/** The URI to fetch an individual voicemail from a given provider. */
public static final Uri CONTENT_URI_PROVIDER_ID_QUERY =
Uri.parse("content://" + AUTHORITY + "/voicemail/provider/");
/** Broadcast intent when a new voicemail record is inserted. */
public static final String ACTION_NEW_VOICEMAIL = "android.intent.action.NEW_VOICEMAIL";
/**
* Extra included in {@value Intent#ACTION_PROVIDER_CHANGED} and {@value #ACTION_NEW_VOICEMAIL}
* broadcast intents to indicate the package that caused the change in content provider.
* <p>
* Receivers of the broadcast can use this field to determine if this is a self change.
*/
public static final String EXTRA_CHANGED_BY =
"com.android.providers.voicemail.changed_by";
/** The different tables defined by the content provider. */
public static final class Tables {
/** The table containing voicemail information. */
public static final class Voicemails {
public static final String NAME = "voicemails";
/** The mime type for a collection of voicemails. */
public static final String DIR_TYPE =
"vnd.android.cursor.dir/voicemails";
/** The different columns contained within the voicemail table. */
public static final class Columns {
public static final String _ID = "_id";
public static final String _DATA = "_data";
public static final String _DATA_FILE_EXISTS = "_data_file_exists";
public static final String NUMBER = "number";
public static final String DATE = "date";
public static final String DURATION = "duration";
public static final String PROVIDER = "provider";
public static final String PROVIDER_DATA = "provider_data";
public static final String DATA_MIME_TYPE = "data_mime_type";
public static final String READ_STATUS = "read_status";
/**
* Current mailbox state of the message.
* <p>
* Legal values: 0(Inbox)/1(Deleted)/2(Undeleted).
*/
public static final String STATE = "state";
}
}
}
}

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