Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.voicemail.common.core;
     18 
     19 import com.example.android.voicemail.common.logging.Logger;
     20 import com.example.android.voicemail.common.utils.CloseUtils;
     21 import com.example.android.voicemail.common.utils.DbQueryUtils;
     22 
     23 import android.content.ContentResolver;
     24 import android.content.ContentUris;
     25 import android.content.ContentValues;
     26 import android.content.Context;
     27 import android.database.Cursor;
     28 import android.net.Uri;
     29 import android.provider.VoicemailContract;
     30 import android.provider.VoicemailContract.Voicemails;
     31 
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.io.OutputStream;
     35 import java.util.ArrayList;
     36 import java.util.List;
     37 
     38 /**
     39  * Implementation of the {@link VoicemailProviderHelper} interface.
     40  */
     41 public final class VoicemailProviderHelpers implements VoicemailProviderHelper {
     42     private static final Logger logger = Logger.getLogger(VoicemailProviderHelpers.class);
     43 
     44     /** Full projection on the voicemail table, giving us all the columns. */
     45     private static final String[] FULL_PROJECTION = new String[] {
     46             Voicemails._ID,
     47             Voicemails.HAS_CONTENT,
     48             Voicemails.NUMBER,
     49             Voicemails.DURATION,
     50             Voicemails.DATE,
     51             Voicemails.SOURCE_PACKAGE,
     52             Voicemails.SOURCE_DATA,
     53             Voicemails.IS_READ
     54     };
     55 
     56     private final ContentResolver mContentResolver;
     57     private final Uri mBaseUri;
     58 
     59     /**
     60      * Creates an instance of {@link VoicemailProviderHelpers} that wraps the supplied content
     61      * provider.
     62      *
     63      * @param contentResolver the ContentResolver used for opening the output stream to read and
     64      *            write to the file
     65      */
     66     private VoicemailProviderHelpers(Uri baseUri, ContentResolver contentResolver) {
     67         mContentResolver = contentResolver;
     68         mBaseUri = baseUri;
     69     }
     70 
     71     /**
     72      * Constructs a VoicemailProviderHelper with full access to all voicemails.
     73      * <p>
     74      * Requires the manifest permissions
     75      * <code>com.android.providers.voicemail.permission.READ_WRITE_ALL_VOICEMAIL</code> and
     76      * <code>com.android.providers.voicemail.permission.READ_WRITE_OWN_VOICEMAIL</code>.
     77      */
     78     public static VoicemailProviderHelper createFullVoicemailProvider(Context context) {
     79         return new VoicemailProviderHelpers(Voicemails.CONTENT_URI, context.getContentResolver());
     80     }
     81 
     82     /**
     83      * Constructs a VoicemailProviderHelper with limited access to voicemails created by this
     84      * source.
     85      * <p>
     86      * Requires the manifest permission
     87      * <code>com.android.providers.voicemail.permission.READ_WRITE_OWN_VOICEMAIL</code>.
     88      */
     89     public static VoicemailProviderHelper createPackageScopedVoicemailProvider(Context context) {
     90         return new VoicemailProviderHelpers(Voicemails.buildSourceUri(context.getPackageName()),
     91                 context.getContentResolver());
     92     }
     93 
     94     @Override
     95     public Uri insert(Voicemail voicemail) {
     96         check(!voicemail.hasId(), "Inserted voicemails must not have an id", voicemail);
     97         check(voicemail.hasTimestampMillis(), "Inserted voicemails must have a timestamp",
     98                 voicemail);
     99         check(voicemail.hasNumber(), "Inserted voicemails must have a number", voicemail);
    100         logger.d(String.format("Inserting new voicemail: %s", voicemail));
    101         ContentValues contentValues = getContentValues(voicemail);
    102         if (!voicemail.hasRead()) {
    103             // If is_read is not set then set it to false as default value.
    104             contentValues.put(Voicemails.IS_READ, 0);
    105         }
    106         return mContentResolver.insert(mBaseUri, contentValues);
    107     }
    108 
    109     @Override
    110     public int update(Uri uri, Voicemail voicemail) {
    111         check(!voicemail.hasUri(), "Can't update the Uri of a voicemail", voicemail);
    112         logger.d("Updating voicemail: " + voicemail + " for uri: " + uri);
    113         ContentValues values = getContentValues(voicemail);
    114         return mContentResolver.update(uri, values, null, null);
    115     }
    116 
    117     @Override
    118     public void setVoicemailContent(Uri voicemailUri, InputStream inputStream, String mimeType)
    119             throws IOException {
    120         setVoicemailContent(voicemailUri, null, inputStream, mimeType);
    121     }
    122 
    123     @Override
    124     public void setVoicemailContent(Uri voicemailUri, byte[] inputBytes, String mimeType)
    125             throws IOException {
    126         setVoicemailContent(voicemailUri, inputBytes, null, mimeType);
    127     }
    128 
    129     private void setVoicemailContent(Uri voicemailUri, byte[] inputBytes, InputStream inputStream,
    130             String mimeType) throws IOException {
    131         if (inputBytes != null && inputStream != null) {
    132             throw new IllegalArgumentException("Both inputBytes & inputStream non-null. Don't" +
    133                     " know which one to use.");
    134         }
    135 
    136         logger.d(String.format("Writing new voicemail content: %s", voicemailUri));
    137         OutputStream outputStream = null;
    138         try {
    139             outputStream = mContentResolver.openOutputStream(voicemailUri);
    140             if (inputBytes != null) {
    141                 outputStream.write(inputBytes);
    142             } else if (inputStream != null) {
    143                 copyStreamData(inputStream, outputStream);
    144             }
    145         } finally {
    146             CloseUtils.closeQuietly(outputStream);
    147         }
    148         // Update mime_type & has_content after we are done with file update.
    149         ContentValues values = new ContentValues();
    150         values.put(Voicemails.MIME_TYPE, mimeType);
    151         values.put(Voicemails.HAS_CONTENT, true);
    152         int updatedCount = mContentResolver.update(voicemailUri, values, null, null);
    153         if (updatedCount != 1) {
    154             throw new IOException("Updating voicemail should have updated 1 row, was: "
    155                     + updatedCount);
    156         }
    157     }
    158 
    159     @Override
    160     public Voicemail findVoicemailBySourceData(String sourceData) {
    161         Cursor cursor = null;
    162         try {
    163             cursor = mContentResolver.query(mBaseUri, FULL_PROJECTION,
    164                     DbQueryUtils.getEqualityClause(Voicemails.SOURCE_DATA, sourceData),
    165                     null, null);
    166             if (cursor.getCount() != 1) {
    167                 logger.w("Expected 1 voicemail matching sourceData " + sourceData + ", got " +
    168                         cursor.getCount());
    169                 return null;
    170             }
    171             cursor.moveToFirst();
    172             return getVoicemailFromCursor(cursor);
    173         } finally {
    174             CloseUtils.closeQuietly(cursor);
    175         }
    176     }
    177 
    178     @Override
    179     public Voicemail findVoicemailByUri(Uri uri) {
    180         Cursor cursor = null;
    181         try {
    182             cursor = mContentResolver.query(uri, FULL_PROJECTION, null, null, null);
    183             if (cursor.getCount() != 1) {
    184                 logger.w("Expected 1 voicemail matching uri " + uri + ", got " + cursor.getCount());
    185                 return null;
    186             }
    187             cursor.moveToFirst();
    188             Voicemail voicemail = getVoicemailFromCursor(cursor);
    189             // Make sure this is an exact match.
    190             if (voicemail.getUri().equals(uri)) {
    191                 return voicemail;
    192             } else {
    193                 logger.w("Queried uri: " + uri + " do not represent a unique voicemail record.");
    194                 return null;
    195             }
    196         } finally {
    197             CloseUtils.closeQuietly(cursor);
    198         }
    199     }
    200 
    201     @Override
    202     public Uri getUriForVoicemailWithId(long id) {
    203         return ContentUris.withAppendedId(mBaseUri, id);
    204     }
    205 
    206     /**
    207      * Checks that an assertion is true.
    208      *
    209      * @throws IllegalArgumentException if the assertion is false, along with a suitable message
    210      *             including a toString() representation of the voicemail
    211      */
    212     private void check(boolean assertion, String message, Voicemail voicemail) {
    213         if (!assertion) {
    214             throw new IllegalArgumentException(message + ": " + voicemail);
    215         }
    216     }
    217 
    218     @Override
    219     public int deleteAll() {
    220         logger.i(String.format("Deleting all voicemails"));
    221         return mContentResolver.delete(mBaseUri, "", new String[0]);
    222     }
    223 
    224     @Override
    225     public List<Voicemail> getAllVoicemails() {
    226         return getAllVoicemails(null, null, SortOrder.DEFAULT);
    227     }
    228 
    229     @Override
    230     public List<Voicemail> getAllVoicemails(VoicemailFilter filter,
    231             String sortColumn, SortOrder sortOrder) {
    232         logger.i(String.format("Fetching all voicemails"));
    233         Cursor cursor = null;
    234         try {
    235             cursor = mContentResolver.query(mBaseUri, FULL_PROJECTION,
    236                     filter != null ? filter.getWhereClause() : null,
    237                     null, getSortBy(sortColumn, sortOrder));
    238             List<Voicemail> results = new ArrayList<Voicemail>(cursor.getCount());
    239             while (cursor.moveToNext()) {
    240                 // A performance optimisation is possible here.
    241                 // The helper method extracts the column indices once every time it is called,
    242                 // whilst
    243                 // we could extract them all up front (without the benefit of the re-use of the
    244                 // helper
    245                 // method code).
    246                 // At the moment I'm pretty sure the benefits outweigh the costs, so leaving as-is.
    247                 results.add(getVoicemailFromCursor(cursor));
    248             }
    249             return results;
    250         } finally {
    251             CloseUtils.closeQuietly(cursor);
    252         }
    253     }
    254 
    255     private String getSortBy(String column, SortOrder sortOrder) {
    256         if (column == null) {
    257             return null;
    258         }
    259         switch (sortOrder) {
    260             case ASCENDING:
    261                 return column + " ASC";
    262             case DESCENDING:
    263                 return column + " DESC";
    264             case DEFAULT:
    265                 return column;
    266         }
    267         // Should never reach here.
    268         return null;
    269     }
    270 
    271     private VoicemailImpl getVoicemailFromCursor(Cursor cursor) {
    272         long id = cursor.getLong(cursor.getColumnIndexOrThrow(Voicemails._ID));
    273         String sourcePackage = cursor.getString(
    274                 cursor.getColumnIndexOrThrow(Voicemails.SOURCE_PACKAGE));
    275         VoicemailImpl voicemail = VoicemailImpl
    276                 .createEmptyBuilder()
    277                 .setTimestamp(cursor.getLong(cursor.getColumnIndexOrThrow(Voicemails.DATE)))
    278                 .setNumber(cursor.getString(cursor.getColumnIndexOrThrow(Voicemails.NUMBER)))
    279                 .setId(id)
    280                 .setDuration(cursor.getLong(cursor.getColumnIndexOrThrow(Voicemails.DURATION)))
    281                 .setSourcePackage(sourcePackage)
    282                 .setSourceData(cursor.getString(
    283                         cursor.getColumnIndexOrThrow(Voicemails.SOURCE_DATA)))
    284                 .setUri(buildUriWithSourcePackage(id, sourcePackage))
    285                 .setHasContent(cursor.getInt(
    286                         cursor.getColumnIndexOrThrow(Voicemails.HAS_CONTENT)) == 1)
    287                 .setIsRead(cursor.getInt(cursor.getColumnIndexOrThrow(Voicemails.IS_READ)) == 1)
    288                 .build();
    289         return voicemail;
    290     }
    291 
    292     private Uri buildUriWithSourcePackage(long id, String sourcePackage) {
    293         return ContentUris.withAppendedId(Voicemails.buildSourceUri(sourcePackage), id);
    294     }
    295 
    296     /**
    297      * Maps structured {@link Voicemail} to {@link ContentValues} understood by content provider.
    298      */
    299     private ContentValues getContentValues(Voicemail voicemail) {
    300         ContentValues contentValues = new ContentValues();
    301         if (voicemail.hasTimestampMillis()) {
    302             contentValues.put(Voicemails.DATE, String.valueOf(voicemail.getTimestampMillis()));
    303         }
    304         if (voicemail.hasNumber()) {
    305             contentValues.put(Voicemails.NUMBER, voicemail.getNumber());
    306         }
    307         if (voicemail.hasDuration()) {
    308             contentValues.put(Voicemails.DURATION, String.valueOf(voicemail.getDuration()));
    309         }
    310         if (voicemail.hasSourcePackage()) {
    311             contentValues.put(Voicemails.SOURCE_PACKAGE, voicemail.getSourcePackage());
    312         }
    313         if (voicemail.hasSourceData()) {
    314             contentValues.put(Voicemails.SOURCE_DATA, voicemail.getSourceData());
    315         }
    316         if (voicemail.hasRead()) {
    317             contentValues.put(Voicemails.IS_READ, voicemail.isRead() ? 1 : 0);
    318         }
    319         return contentValues;
    320     }
    321 
    322     private void copyStreamData(InputStream in, OutputStream out) throws IOException {
    323         byte[] data = new byte[8 * 1024];
    324         int numBytes;
    325         while ((numBytes = in.read(data)) > 0) {
    326             out.write(data, 0, numBytes);
    327         }
    328 
    329     }
    330 }
    331