summaryrefslogtreecommitdiff
path: root/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java
blob: 7abf9a72ca38207a0055e2bb77a37ec4c81c78e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
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<Void, Void, String>() {
                    @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<Void, Void, Boolean>() {
                    @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<Void, Void, Uri>() {
                    @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);
    }
}