From c70343becd3734fd380e0d20f05b7605e6233d43 Mon Sep 17 00:00:00 2001 From: Sarmad Hashmi Date: Thu, 10 Mar 2016 20:22:43 -0800 Subject: Refactor voicemail archive code. +Created new class for all AsyncTasks and listener interfaces. +Separated the archive code into separate methods +Added 7 tests BUG=22797391 Change-Id: I789125328bf079846e34c5fd57fd0d1c67dc6266 --- .../dialer/voicemail/VoicemailAsyncTaskUtil.java | 346 +++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java (limited to 'src/com/android/dialer/voicemail') diff --git a/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java b/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java new file mode 100644 index 000000000..7abf9a72c --- /dev/null +++ b/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2016 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.dialer.voicemail; + +import com.android.contacts.common.testing.NeededForTesting; +import com.android.dialer.calllog.CallLogQuery; +import com.android.dialer.database.VoicemailArchiveContract; +import com.android.dialer.util.AsyncTaskExecutor; +import com.android.dialer.util.AsyncTaskExecutors; +import com.google.common.base.Preconditions; +import com.google.common.io.ByteStreams; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.CallLog; +import android.provider.VoicemailContract; +import android.util.Log; +import com.android.common.io.MoreCloseables; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.annotation.Nullable; + +/** + * Class containing asynchronous tasks for voicemails. + */ +@NeededForTesting +public class VoicemailAsyncTaskUtil { + private static final String TAG = "VoicemailAsyncTaskUtil"; + + /** The enumeration of {@link AsyncTask} objects we use in this class. */ + public enum Tasks { + GET_VOICEMAIL_FILE_PATH, + SET_VOICEMAIL_ARCHIVE_STATUS, + ARCHIVE_VOICEMAIL_CONTENT + } + + @NeededForTesting + public interface OnArchiveVoicemailListener { + /** + * Called after the voicemail has been archived. + * + * @param archivedVoicemailUri the URI of the archived voicemail + */ + void onArchiveVoicemail(@Nullable Uri archivedVoicemailUri); + } + + @NeededForTesting + public interface OnSetVoicemailArchiveStatusListener { + /** + * Called after the voicemail archived_by_user column is updated. + * + * @param success whether the update was successful or not + */ + void onSetVoicemailArchiveStatus(boolean success); + } + + @NeededForTesting + public interface OnGetArchivedVoicemailFilePathListener { + /** + * Called after the voicemail file path is obtained. + * + * @param filePath the file path of the archived voicemail + */ + void onGetArchivedVoicemailFilePath(@Nullable String filePath); + } + + private final ContentResolver mResolver; + private final AsyncTaskExecutor mAsyncTaskExecutor; + + @NeededForTesting + public VoicemailAsyncTaskUtil(ContentResolver contentResolver) { + mResolver = Preconditions.checkNotNull(contentResolver); + mAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); + } + + /** + * Returns the archived voicemail file path. + */ + @NeededForTesting + public void getVoicemailFilePath( + final OnGetArchivedVoicemailFilePathListener listener, + final Uri voicemailUri) { + Preconditions.checkNotNull(listener); + Preconditions.checkNotNull(voicemailUri); + mAsyncTaskExecutor.submit(Tasks.GET_VOICEMAIL_FILE_PATH, + new AsyncTask() { + @Nullable + @Override + protected String doInBackground(Void... params) { + try (Cursor cursor = mResolver.query(voicemailUri, + new String[]{VoicemailArchiveContract.VoicemailArchive._DATA}, + null, null, null)) { + if (hasContent(cursor)) { + return cursor.getString(cursor.getColumnIndex( + VoicemailArchiveContract.VoicemailArchive._DATA)); + } + } + return null; + } + + @Override + protected void onPostExecute(String filePath) { + listener.onGetArchivedVoicemailFilePath(filePath); + } + }); + } + + /** + * Updates the archived_by_user flag of the archived voicemail. + */ + @NeededForTesting + public void setVoicemailArchiveStatus( + final OnSetVoicemailArchiveStatusListener listener, + final Uri voicemailUri, + final boolean archivedByUser) { + Preconditions.checkNotNull(listener); + Preconditions.checkNotNull(voicemailUri); + mAsyncTaskExecutor.submit(Tasks.SET_VOICEMAIL_ARCHIVE_STATUS, + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + ContentValues values = new ContentValues(1); + values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, + archivedByUser); + return mResolver.update(voicemailUri, values, null, null) > 0; + } + + @Override + protected void onPostExecute(Boolean success) { + listener.onSetVoicemailArchiveStatus(success); + } + }); + } + + /** + * Checks if a voicemail has already been archived, if so, return the previously archived URI. + * Otherwise, copy the voicemail information to the local dialer database. If archive was + * successful, archived voicemail URI is returned to listener, otherwise null. + */ + @NeededForTesting + public void archiveVoicemailContent( + final OnArchiveVoicemailListener listener, + final Uri voicemailUri) { + Preconditions.checkNotNull(listener); + Preconditions.checkNotNull(voicemailUri); + mAsyncTaskExecutor.submit(Tasks.ARCHIVE_VOICEMAIL_CONTENT, + new AsyncTask() { + @Nullable + @Override + protected Uri doInBackground(Void... params) { + Uri archivedVoicemailUri = getArchivedVoicemailUri(voicemailUri); + + // If previously archived, return uri, otherwise archive everything. + if (archivedVoicemailUri != null) { + return archivedVoicemailUri; + } + + // Combine call log and voicemail content info. + ContentValues values = getVoicemailContentValues(voicemailUri); + if (values == null) { + return null; + } + + Uri insertedVoicemailUri = mResolver.insert( + VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, values); + if (insertedVoicemailUri == null) { + return null; + } + + // Copy voicemail content to a new file. + boolean copiedFile = false; + try (InputStream inputStream = mResolver.openInputStream(voicemailUri); + OutputStream outputStream = + mResolver.openOutputStream(insertedVoicemailUri)) { + if (inputStream != null && outputStream != null) { + ByteStreams.copy(inputStream, outputStream); + copiedFile = true; + return insertedVoicemailUri; + } + } catch (IOException e) { + Log.w(TAG, "Failed to copy voicemail content to new file: " + + e.toString()); + } finally { + if (!copiedFile) { + // Roll back insert if the voicemail content was not copied. + mResolver.delete(insertedVoicemailUri, null, null); + } + } + return null; + } + + @Override + protected void onPostExecute(Uri archivedVoicemailUri) { + listener.onArchiveVoicemail(archivedVoicemailUri); + } + }); + } + + /** + * Helper method to get the archived URI of a voicemail. + * + * @param voicemailUri a {@link android.provider.VoicemailContract.Voicemails#CONTENT_URI} URI. + * @return the URI of the archived voicemail or {@code null} + */ + @Nullable + private Uri getArchivedVoicemailUri(Uri voicemailUri) { + try (Cursor cursor = getArchiveExistsCursor(voicemailUri)) { + if (hasContent(cursor)) { + return VoicemailArchiveContract.VoicemailArchive + .buildWithId(cursor.getInt(cursor.getColumnIndex( + VoicemailArchiveContract.VoicemailArchive._ID))); + } + } + return null; + } + + /** + * Helper method to make a copy of all the values needed to display a voicemail. + * + * @param voicemailUri a {@link VoicemailContract.Voicemails#CONTENT_URI} URI. + * @return the combined call log and voicemail values for the given URI, or {@code null} + */ + @Nullable + private ContentValues getVoicemailContentValues(Uri voicemailUri) { + try (Cursor callLogInfo = getCallLogInfoCursor(voicemailUri); + Cursor contentInfo = getContentInfoCursor(voicemailUri)) { + + if (hasContent(callLogInfo) && hasContent(contentInfo)) { + // Create values to insert into database. + ContentValues values = new ContentValues(); + + // Insert voicemail call log info. + values.put(VoicemailArchiveContract.VoicemailArchive.COUNTRY_ISO, + callLogInfo.getString(CallLogQuery.COUNTRY_ISO)); + values.put(VoicemailArchiveContract.VoicemailArchive.GEOCODED_LOCATION, + callLogInfo.getString(CallLogQuery.GEOCODED_LOCATION)); + values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NAME, + callLogInfo.getString(CallLogQuery.CACHED_NAME)); + values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_TYPE, + callLogInfo.getInt(CallLogQuery.CACHED_NUMBER_TYPE)); + values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_LABEL, + callLogInfo.getString(CallLogQuery.CACHED_NUMBER_LABEL)); + values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_LOOKUP_URI, + callLogInfo.getString(CallLogQuery.CACHED_LOOKUP_URI)); + values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_MATCHED_NUMBER, + callLogInfo.getString(CallLogQuery.CACHED_MATCHED_NUMBER)); + values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NORMALIZED_NUMBER, + callLogInfo.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER)); + values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_FORMATTED_NUMBER, + callLogInfo.getString(CallLogQuery.CACHED_FORMATTED_NUMBER)); + values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER_PRESENTATION, + callLogInfo.getInt(CallLogQuery.NUMBER_PRESENTATION)); + values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_COMPONENT_NAME, + callLogInfo.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME)); + values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_ID, + callLogInfo.getString(CallLogQuery.ACCOUNT_ID)); + values.put(VoicemailArchiveContract.VoicemailArchive.FEATURES, + callLogInfo.getInt(CallLogQuery.FEATURES)); + values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_PHOTO_URI, + callLogInfo.getString(CallLogQuery.CACHED_PHOTO_URI)); + + // Insert voicemail content info. + values.put(VoicemailArchiveContract.VoicemailArchive.SERVER_ID, + contentInfo.getInt(contentInfo.getColumnIndex( + VoicemailContract.Voicemails._ID))); + values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER, + contentInfo.getString(contentInfo.getColumnIndex( + VoicemailContract.Voicemails.NUMBER))); + values.put(VoicemailArchiveContract.VoicemailArchive.DATE, + contentInfo.getLong(contentInfo.getColumnIndex( + VoicemailContract.Voicemails.DATE))); + values.put(VoicemailArchiveContract.VoicemailArchive.DURATION, + contentInfo.getLong(contentInfo.getColumnIndex( + VoicemailContract.Voicemails.DURATION))); + values.put(VoicemailArchiveContract.VoicemailArchive.MIME_TYPE, + contentInfo.getString(contentInfo.getColumnIndex( + VoicemailContract.Voicemails.MIME_TYPE))); + values.put(VoicemailArchiveContract.VoicemailArchive.TRANSCRIPTION, + contentInfo.getString(contentInfo.getColumnIndex( + VoicemailContract.Voicemails.TRANSCRIPTION))); + + // Achived is false by default because it is updated after insertion. + values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, false); + + return values; + } + } + return null; + } + + private boolean hasContent(@Nullable Cursor cursor) { + return cursor != null && cursor.moveToFirst(); + } + + @Nullable + private Cursor getCallLogInfoCursor(Uri voicemailUri) { + return mResolver.query( + ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, + ContentUris.parseId(voicemailUri)), + CallLogQuery._PROJECTION, null, null, null); + } + + @Nullable + private Cursor getContentInfoCursor(Uri voicemailUri) { + return mResolver.query(voicemailUri, + new String[] { + VoicemailContract.Voicemails._ID, + VoicemailContract.Voicemails.NUMBER, + VoicemailContract.Voicemails.DATE, + VoicemailContract.Voicemails.DURATION, + VoicemailContract.Voicemails.MIME_TYPE, + VoicemailContract.Voicemails.TRANSCRIPTION, + }, null, null, null); + } + + @Nullable + private Cursor getArchiveExistsCursor(Uri voicemailUri) { + return mResolver.query(VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, + new String[] {VoicemailArchiveContract.VoicemailArchive._ID}, + VoicemailArchiveContract.VoicemailArchive.SERVER_ID + "=" + + ContentUris.parseId(voicemailUri), + null, + null); + } +} -- cgit v1.2.3