blob: 27c7f695b6f46837dfe58b499cac432566354913 [file] [log] [blame]
/*
* 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 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 android.provider.VoicemailContract;
import android.provider.VoicemailContract.Voicemails;
import java.io.IOException;
import java.io.InputStream;
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[] {
Voicemails._ID,
Voicemails.HAS_CONTENT,
Voicemails.NUMBER,
Voicemails.DURATION,
Voicemails.DATE,
Voicemails.SOURCE_PACKAGE,
Voicemails.SOURCE_DATA,
Voicemails.IS_READ
};
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(Voicemails.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) {
return new VoicemailProviderHelpers(Voicemails.buildSourceUri(context.getPackageName()),
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);
if (!voicemail.hasRead()) {
// If is_read is not set then set it to false as default value.
contentValues.put(Voicemails.IS_READ, 0);
}
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 void setVoicemailContent(Uri voicemailUri, InputStream inputStream, String mimeType)
throws IOException {
setVoicemailContent(voicemailUri, null, inputStream, mimeType);
}
@Override
public void setVoicemailContent(Uri voicemailUri, byte[] inputBytes, String mimeType)
throws IOException {
setVoicemailContent(voicemailUri, inputBytes, null, mimeType);
}
private void setVoicemailContent(Uri voicemailUri, byte[] inputBytes, InputStream inputStream,
String mimeType) throws IOException {
if (inputBytes != null && inputStream != null) {
throw new IllegalArgumentException("Both inputBytes & inputStream non-null. Don't" +
" know which one to use.");
}
logger.d(String.format("Writing new voicemail content: %s", voicemailUri));
OutputStream outputStream = null;
try {
outputStream = mContentResolver.openOutputStream(voicemailUri);
if (inputBytes != null) {
outputStream.write(inputBytes);
} else if (inputStream != null) {
copyStreamData(inputStream, outputStream);
}
} finally {
CloseUtils.closeQuietly(outputStream);
}
// Update mime_type & has_content after we are done with file update.
ContentValues values = new ContentValues();
values.put(Voicemails.MIME_TYPE, mimeType);
values.put(Voicemails.HAS_CONTENT, true);
int updatedCount = mContentResolver.update(voicemailUri, values, null, null);
if (updatedCount != 1) {
throw new IOException("Updating voicemail should have updated 1 row, was: "
+ updatedCount);
}
}
@Override
public Voicemail findVoicemailBySourceData(String sourceData) {
Cursor cursor = null;
try {
cursor = mContentResolver.query(mBaseUri, FULL_PROJECTION,
DbQueryUtils.getEqualityClause(Voicemails.SOURCE_DATA, sourceData),
null, null);
if (cursor.getCount() != 1) {
logger.w("Expected 1 voicemail matching sourceData " + sourceData + ", 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(Voicemails._ID));
String sourcePackage = cursor.getString(
cursor.getColumnIndexOrThrow(Voicemails.SOURCE_PACKAGE));
VoicemailImpl voicemail = VoicemailImpl
.createEmptyBuilder()
.setTimestamp(cursor.getLong(cursor.getColumnIndexOrThrow(Voicemails.DATE)))
.setNumber(cursor.getString(cursor.getColumnIndexOrThrow(Voicemails.NUMBER)))
.setId(id)
.setDuration(cursor.getLong(cursor.getColumnIndexOrThrow(Voicemails.DURATION)))
.setSourcePackage(sourcePackage)
.setSourceData(cursor.getString(
cursor.getColumnIndexOrThrow(Voicemails.SOURCE_DATA)))
.setUri(buildUriWithSourcePackage(id, sourcePackage))
.setHasContent(cursor.getInt(
cursor.getColumnIndexOrThrow(Voicemails.HAS_CONTENT)) == 1)
.setIsRead(cursor.getInt(cursor.getColumnIndexOrThrow(Voicemails.IS_READ)) == 1)
.build();
return voicemail;
}
private Uri buildUriWithSourcePackage(long id, String sourcePackage) {
return ContentUris.withAppendedId(Voicemails.buildSourceUri(sourcePackage), id);
}
/**
* 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(Voicemails.DATE, String.valueOf(voicemail.getTimestampMillis()));
}
if (voicemail.hasNumber()) {
contentValues.put(Voicemails.NUMBER, voicemail.getNumber());
}
if (voicemail.hasDuration()) {
contentValues.put(Voicemails.DURATION, String.valueOf(voicemail.getDuration()));
}
if (voicemail.hasSourcePackage()) {
contentValues.put(Voicemails.SOURCE_PACKAGE, voicemail.getSourcePackage());
}
if (voicemail.hasSourceData()) {
contentValues.put(Voicemails.SOURCE_DATA, voicemail.getSourceData());
}
if (voicemail.hasRead()) {
contentValues.put(Voicemails.IS_READ, voicemail.isRead() ? 1 : 0);
}
return contentValues;
}
private void copyStreamData(InputStream in, OutputStream out) throws IOException {
byte[] data = new byte[8 * 1024];
int numBytes;
while ((numBytes = in.read(data)) > 0) {
out.write(data, 0, numBytes);
}
}
}