/* * 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); } } }