Home | History | Annotate | Download | only in voicemail
      1 /*
      2  * Copyright (C) 2016 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.android.dialer.voicemail;
     18 
     19 import com.android.contacts.common.testing.NeededForTesting;
     20 import com.android.dialer.calllog.CallLogQuery;
     21 import com.android.dialer.database.VoicemailArchiveContract;
     22 import com.android.dialer.util.AsyncTaskExecutor;
     23 import com.android.dialer.util.AsyncTaskExecutors;
     24 import com.google.common.base.Preconditions;
     25 import com.google.common.io.ByteStreams;
     26 
     27 import android.content.ContentResolver;
     28 import android.content.ContentUris;
     29 import android.content.ContentValues;
     30 import android.database.Cursor;
     31 import android.net.Uri;
     32 import android.os.AsyncTask;
     33 import android.provider.CallLog;
     34 import android.provider.VoicemailContract;
     35 import android.util.Log;
     36 import com.android.common.io.MoreCloseables;
     37 
     38 import java.io.IOException;
     39 import java.io.InputStream;
     40 import java.io.OutputStream;
     41 
     42 import javax.annotation.Nullable;
     43 
     44 /**
     45  * Class containing asynchronous tasks for voicemails.
     46  */
     47 @NeededForTesting
     48 public class VoicemailAsyncTaskUtil {
     49     private static final String TAG = "VoicemailAsyncTaskUtil";
     50 
     51     /** The enumeration of {@link AsyncTask} objects we use in this class. */
     52     public enum Tasks {
     53         GET_VOICEMAIL_FILE_PATH,
     54         SET_VOICEMAIL_ARCHIVE_STATUS,
     55         ARCHIVE_VOICEMAIL_CONTENT
     56     }
     57 
     58     @NeededForTesting
     59     public interface OnArchiveVoicemailListener {
     60         /**
     61          * Called after the voicemail has been archived.
     62          *
     63          * @param archivedVoicemailUri the URI of the archived voicemail
     64          */
     65         void onArchiveVoicemail(@Nullable Uri archivedVoicemailUri);
     66     }
     67 
     68     @NeededForTesting
     69     public interface OnSetVoicemailArchiveStatusListener {
     70         /**
     71          * Called after the voicemail archived_by_user column is updated.
     72          *
     73          * @param success whether the update was successful or not
     74          */
     75         void onSetVoicemailArchiveStatus(boolean success);
     76     }
     77 
     78     @NeededForTesting
     79     public interface OnGetArchivedVoicemailFilePathListener {
     80         /**
     81          * Called after the voicemail file path is obtained.
     82          *
     83          * @param filePath the file path of the archived voicemail
     84          */
     85         void onGetArchivedVoicemailFilePath(@Nullable String filePath);
     86     }
     87 
     88     private final ContentResolver mResolver;
     89     private final AsyncTaskExecutor mAsyncTaskExecutor;
     90 
     91     @NeededForTesting
     92     public VoicemailAsyncTaskUtil(ContentResolver contentResolver) {
     93         mResolver = Preconditions.checkNotNull(contentResolver);
     94         mAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor();
     95     }
     96 
     97     /**
     98      * Returns the archived voicemail file path.
     99      */
    100     @NeededForTesting
    101     public void getVoicemailFilePath(
    102             final OnGetArchivedVoicemailFilePathListener listener,
    103             final Uri voicemailUri) {
    104         Preconditions.checkNotNull(listener);
    105         Preconditions.checkNotNull(voicemailUri);
    106         mAsyncTaskExecutor.submit(Tasks.GET_VOICEMAIL_FILE_PATH,
    107                 new AsyncTask<Void, Void, String>() {
    108                     @Nullable
    109                     @Override
    110                     protected String doInBackground(Void... params) {
    111                         try (Cursor cursor = mResolver.query(voicemailUri,
    112                                 new String[]{VoicemailArchiveContract.VoicemailArchive._DATA},
    113                                 null, null, null)) {
    114                             if (hasContent(cursor)) {
    115                                 return cursor.getString(cursor.getColumnIndex(
    116                                         VoicemailArchiveContract.VoicemailArchive._DATA));
    117                             }
    118                         }
    119                         return null;
    120                     }
    121 
    122                     @Override
    123                     protected void onPostExecute(String filePath) {
    124                         listener.onGetArchivedVoicemailFilePath(filePath);
    125                     }
    126                 });
    127     }
    128 
    129     /**
    130      * Updates the archived_by_user flag of the archived voicemail.
    131      */
    132     @NeededForTesting
    133     public void setVoicemailArchiveStatus(
    134             final OnSetVoicemailArchiveStatusListener listener,
    135             final Uri voicemailUri,
    136             final boolean archivedByUser) {
    137         Preconditions.checkNotNull(listener);
    138         Preconditions.checkNotNull(voicemailUri);
    139         mAsyncTaskExecutor.submit(Tasks.SET_VOICEMAIL_ARCHIVE_STATUS,
    140                 new AsyncTask<Void, Void, Boolean>() {
    141                     @Override
    142                     protected Boolean doInBackground(Void... params) {
    143                         ContentValues values = new ContentValues(1);
    144                         values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED,
    145                                 archivedByUser);
    146                         return mResolver.update(voicemailUri, values, null, null) > 0;
    147                     }
    148 
    149                     @Override
    150                     protected void onPostExecute(Boolean success) {
    151                         listener.onSetVoicemailArchiveStatus(success);
    152                     }
    153                 });
    154     }
    155 
    156     /**
    157      * Checks if a voicemail has already been archived, if so, return the previously archived URI.
    158      * Otherwise, copy the voicemail information to the local dialer database. If archive was
    159      * successful, archived voicemail URI is returned to listener, otherwise null.
    160      */
    161     @NeededForTesting
    162     public void archiveVoicemailContent(
    163             final OnArchiveVoicemailListener listener,
    164             final Uri voicemailUri) {
    165         Preconditions.checkNotNull(listener);
    166         Preconditions.checkNotNull(voicemailUri);
    167         mAsyncTaskExecutor.submit(Tasks.ARCHIVE_VOICEMAIL_CONTENT,
    168                 new AsyncTask<Void, Void, Uri>() {
    169                     @Nullable
    170                     @Override
    171                     protected Uri doInBackground(Void... params) {
    172                         Uri archivedVoicemailUri = getArchivedVoicemailUri(voicemailUri);
    173 
    174                         // If previously archived, return uri, otherwise archive everything.
    175                         if (archivedVoicemailUri != null) {
    176                             return archivedVoicemailUri;
    177                         }
    178 
    179                         // Combine call log and voicemail content info.
    180                         ContentValues values = getVoicemailContentValues(voicemailUri);
    181                         if (values == null) {
    182                             return null;
    183                         }
    184 
    185                         Uri insertedVoicemailUri = mResolver.insert(
    186                                 VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, values);
    187                         if (insertedVoicemailUri == null) {
    188                             return null;
    189                         }
    190 
    191                         // Copy voicemail content to a new file.
    192                         boolean copiedFile = false;
    193                         try (InputStream inputStream = mResolver.openInputStream(voicemailUri);
    194                              OutputStream outputStream =
    195                                      mResolver.openOutputStream(insertedVoicemailUri)) {
    196                             if (inputStream != null && outputStream != null) {
    197                                 ByteStreams.copy(inputStream, outputStream);
    198                                 copiedFile = true;
    199                                 return insertedVoicemailUri;
    200                             }
    201                         } catch (IOException e) {
    202                             Log.w(TAG, "Failed to copy voicemail content to new file: "
    203                                     + e.toString());
    204                         } finally {
    205                             if (!copiedFile) {
    206                                 // Roll back insert if the voicemail content was not copied.
    207                                 mResolver.delete(insertedVoicemailUri, null, null);
    208                             }
    209                         }
    210                         return null;
    211                     }
    212 
    213                     @Override
    214                     protected void onPostExecute(Uri archivedVoicemailUri) {
    215                         listener.onArchiveVoicemail(archivedVoicemailUri);
    216                     }
    217                 });
    218     }
    219 
    220     /**
    221      * Helper method to get the archived URI of a voicemail.
    222      *
    223      * @param voicemailUri a {@link android.provider.VoicemailContract.Voicemails#CONTENT_URI} URI.
    224      * @return the URI of the archived voicemail or {@code null}
    225      */
    226     @Nullable
    227     private Uri getArchivedVoicemailUri(Uri voicemailUri) {
    228         try (Cursor cursor = getArchiveExistsCursor(voicemailUri)) {
    229             if (hasContent(cursor)) {
    230                 return VoicemailArchiveContract.VoicemailArchive
    231                         .buildWithId(cursor.getInt(cursor.getColumnIndex(
    232                                 VoicemailArchiveContract.VoicemailArchive._ID)));
    233             }
    234         }
    235         return null;
    236     }
    237 
    238     /**
    239      * Helper method to make a copy of all the values needed to display a voicemail.
    240      *
    241      * @param voicemailUri a {@link VoicemailContract.Voicemails#CONTENT_URI} URI.
    242      * @return the combined call log and voicemail values for the given URI, or {@code null}
    243      */
    244     @Nullable
    245     private ContentValues getVoicemailContentValues(Uri voicemailUri) {
    246         try (Cursor callLogInfo = getCallLogInfoCursor(voicemailUri);
    247              Cursor contentInfo = getContentInfoCursor(voicemailUri)) {
    248 
    249             if (hasContent(callLogInfo) && hasContent(contentInfo)) {
    250                 // Create values to insert into database.
    251                 ContentValues values = new ContentValues();
    252 
    253                 // Insert voicemail call log info.
    254                 values.put(VoicemailArchiveContract.VoicemailArchive.COUNTRY_ISO,
    255                         callLogInfo.getString(CallLogQuery.COUNTRY_ISO));
    256                 values.put(VoicemailArchiveContract.VoicemailArchive.GEOCODED_LOCATION,
    257                         callLogInfo.getString(CallLogQuery.GEOCODED_LOCATION));
    258                 values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NAME,
    259                         callLogInfo.getString(CallLogQuery.CACHED_NAME));
    260                 values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_TYPE,
    261                         callLogInfo.getInt(CallLogQuery.CACHED_NUMBER_TYPE));
    262                 values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_LABEL,
    263                         callLogInfo.getString(CallLogQuery.CACHED_NUMBER_LABEL));
    264                 values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_LOOKUP_URI,
    265                         callLogInfo.getString(CallLogQuery.CACHED_LOOKUP_URI));
    266                 values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_MATCHED_NUMBER,
    267                         callLogInfo.getString(CallLogQuery.CACHED_MATCHED_NUMBER));
    268                 values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NORMALIZED_NUMBER,
    269                         callLogInfo.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER));
    270                 values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_FORMATTED_NUMBER,
    271                         callLogInfo.getString(CallLogQuery.CACHED_FORMATTED_NUMBER));
    272                 values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER_PRESENTATION,
    273                         callLogInfo.getInt(CallLogQuery.NUMBER_PRESENTATION));
    274                 values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_COMPONENT_NAME,
    275                         callLogInfo.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME));
    276                 values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_ID,
    277                         callLogInfo.getString(CallLogQuery.ACCOUNT_ID));
    278                 values.put(VoicemailArchiveContract.VoicemailArchive.FEATURES,
    279                         callLogInfo.getInt(CallLogQuery.FEATURES));
    280                 values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_PHOTO_URI,
    281                         callLogInfo.getString(CallLogQuery.CACHED_PHOTO_URI));
    282 
    283                 // Insert voicemail content info.
    284                 values.put(VoicemailArchiveContract.VoicemailArchive.SERVER_ID,
    285                         contentInfo.getInt(contentInfo.getColumnIndex(
    286                                 VoicemailContract.Voicemails._ID)));
    287                 values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER,
    288                         contentInfo.getString(contentInfo.getColumnIndex(
    289                                 VoicemailContract.Voicemails.NUMBER)));
    290                 values.put(VoicemailArchiveContract.VoicemailArchive.DATE,
    291                         contentInfo.getLong(contentInfo.getColumnIndex(
    292                                 VoicemailContract.Voicemails.DATE)));
    293                 values.put(VoicemailArchiveContract.VoicemailArchive.DURATION,
    294                         contentInfo.getLong(contentInfo.getColumnIndex(
    295                                 VoicemailContract.Voicemails.DURATION)));
    296                 values.put(VoicemailArchiveContract.VoicemailArchive.MIME_TYPE,
    297                         contentInfo.getString(contentInfo.getColumnIndex(
    298                                 VoicemailContract.Voicemails.MIME_TYPE)));
    299                 values.put(VoicemailArchiveContract.VoicemailArchive.TRANSCRIPTION,
    300                         contentInfo.getString(contentInfo.getColumnIndex(
    301                                 VoicemailContract.Voicemails.TRANSCRIPTION)));
    302 
    303                 // Achived is false by default because it is updated after insertion.
    304                 values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, false);
    305 
    306                 return values;
    307             }
    308         }
    309         return null;
    310     }
    311 
    312     private boolean hasContent(@Nullable Cursor cursor) {
    313         return cursor != null && cursor.moveToFirst();
    314     }
    315 
    316     @Nullable
    317     private Cursor getCallLogInfoCursor(Uri voicemailUri) {
    318         return mResolver.query(
    319                 ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
    320                         ContentUris.parseId(voicemailUri)),
    321                 CallLogQuery._PROJECTION, null, null, null);
    322     }
    323 
    324     @Nullable
    325     private Cursor getContentInfoCursor(Uri voicemailUri) {
    326         return mResolver.query(voicemailUri,
    327                 new String[] {
    328                         VoicemailContract.Voicemails._ID,
    329                         VoicemailContract.Voicemails.NUMBER,
    330                         VoicemailContract.Voicemails.DATE,
    331                         VoicemailContract.Voicemails.DURATION,
    332                         VoicemailContract.Voicemails.MIME_TYPE,
    333                         VoicemailContract.Voicemails.TRANSCRIPTION,
    334                 }, null, null, null);
    335     }
    336 
    337     @Nullable
    338     private Cursor getArchiveExistsCursor(Uri voicemailUri) {
    339         return mResolver.query(VoicemailArchiveContract.VoicemailArchive.CONTENT_URI,
    340                 new String[] {VoicemailArchiveContract.VoicemailArchive._ID},
    341                 VoicemailArchiveContract.VoicemailArchive.SERVER_ID + "="
    342                         + ContentUris.parseId(voicemailUri),
    343                 null,
    344                 null);
    345     }
    346 }
    347