1 /*
2  * Copyright (C) 2017 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 package com.android.voicemail.impl.transcribe;
17 
18 import android.app.job.JobWorkItem;
19 import android.content.Context;
20 import android.support.annotation.VisibleForTesting;
21 import android.util.Pair;
22 import com.android.dialer.logging.DialerImpression;
23 import com.android.voicemail.VoicemailComponent;
24 import com.android.voicemail.impl.VvmLog;
25 import com.android.voicemail.impl.transcribe.TranscriptionService.JobCallback;
26 import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
27 import com.android.voicemail.impl.transcribe.grpc.TranscriptionResponseAsync;
28 import com.google.internal.communications.voicemailtranscription.v1.DonationPreference;
29 import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest;
30 import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus;
31 
32 /**
33  * Background task to get a voicemail transcription using the asynchronous API. The async API works
34  * as follows:
35  *
36  * <ol>
37  *   <li>client uploads voicemail data to the server
38  *   <li>server responds with a transcription-id and an estimated transcription wait time
39  *   <li>client waits appropriate amount of time then begins polling for the result
40  * </ol>
41  *
42  * This implementation blocks until the response or an error is received, even though it is using
43  * the asynchronous server API.
44  */
45 public class TranscriptionTaskAsync extends TranscriptionTask {
46   private static final String TAG = "TranscriptionTaskAsync";
47 
TranscriptionTaskAsync( Context context, JobCallback callback, JobWorkItem workItem, TranscriptionClientFactory clientFactory, TranscriptionConfigProvider configProvider)48   public TranscriptionTaskAsync(
49       Context context,
50       JobCallback callback,
51       JobWorkItem workItem,
52       TranscriptionClientFactory clientFactory,
53       TranscriptionConfigProvider configProvider) {
54     super(context, callback, workItem, clientFactory, configProvider);
55   }
56 
57   @Override
getTranscription()58   protected Pair<String, TranscriptionStatus> getTranscription() {
59     VvmLog.i(TAG, "getTranscription");
60 
61     if (GetTranscriptReceiver.hasPendingAlarm(context)) {
62       // Don't start a transcription while another is still active
63       VvmLog.i(
64           TAG,
65           "getTranscription, pending transcription, postponing transcription of: " + voicemailUri);
66       return new Pair<>(null, null);
67     }
68 
69     TranscribeVoicemailAsyncRequest uploadRequest = getUploadRequest();
70     VvmLog.i(
71         TAG,
72         "getTranscription, uploading voicemail: "
73             + voicemailUri
74             + ", id: "
75             + uploadRequest.getTranscriptionId());
76     TranscriptionResponseAsync uploadResponse =
77         (TranscriptionResponseAsync)
78             sendRequest((client) -> client.sendUploadRequest(uploadRequest));
79 
80     if (cancelled) {
81       VvmLog.i(TAG, "getTranscription, cancelled.");
82       return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
83     } else if (uploadResponse == null) {
84       VvmLog.i(TAG, "getTranscription, failed to upload voicemail.");
85       return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
86     } else if (uploadResponse.isStatusAlreadyExists()) {
87       VvmLog.i(TAG, "getTranscription, transcription already exists.");
88       GetTranscriptReceiver.beginPolling(
89           context,
90           voicemailUri,
91           uploadRequest.getTranscriptionId(),
92           0,
93           configProvider,
94           phoneAccountHandle);
95       return new Pair<>(null, null);
96     } else if (uploadResponse.getTranscriptionId() == null) {
97       VvmLog.i(TAG, "getTranscription, upload error: " + uploadResponse.status);
98       return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
99     } else {
100       VvmLog.i(TAG, "getTranscription, begin polling for: " + uploadResponse.getTranscriptionId());
101       GetTranscriptReceiver.beginPolling(
102           context,
103           voicemailUri,
104           uploadResponse.getTranscriptionId(),
105           uploadResponse.getEstimatedWaitMillis(),
106           configProvider,
107           phoneAccountHandle);
108       // This indicates that the result is not available yet
109       return new Pair<>(null, null);
110     }
111   }
112 
113   @Override
getRequestSentImpression()114   protected DialerImpression.Type getRequestSentImpression() {
115     return DialerImpression.Type.VVM_TRANSCRIPTION_REQUEST_SENT_ASYNC;
116   }
117 
118   @VisibleForTesting
getUploadRequest()119   TranscribeVoicemailAsyncRequest getUploadRequest() {
120     TranscribeVoicemailAsyncRequest.Builder builder =
121         TranscribeVoicemailAsyncRequest.newBuilder()
122             .setVoicemailData(audioData)
123             .setAudioFormat(encoding)
124             .setDonationPreference(
125                 isDonationEnabled() ? DonationPreference.DONATE : DonationPreference.DO_NOT_DONATE);
126     // Generate the transcript id locally if configured to do so, or if voicemail donation is
127     // available (because rating donating voicemails requires locally generated voicemail ids).
128     if (configProvider.useClientGeneratedVoicemailIds()
129         || VoicemailComponent.get(context)
130             .getVoicemailClient()
131             .isVoicemailDonationAvailable(context, phoneAccountHandle)) {
132       // The server currently can't handle repeated transcription id's so if we add the Uri to the
133       // fingerprint (which contains the voicemail id) which is different each time a voicemail is
134       // downloaded.  If this becomes a problem then it should be possible to change the server
135       // behavior to allow id's to be re-used, a bug
136       String salt = voicemailUri.toString();
137       builder.setTranscriptionId(TranscriptionUtils.getFingerprintFor(audioData, salt));
138     }
139     return builder.build();
140   }
141 
isDonationEnabled()142   private boolean isDonationEnabled() {
143     return phoneAccountHandle != null
144         && VoicemailComponent.get(context)
145             .getVoicemailClient()
146             .isVoicemailDonationEnabled(context, phoneAccountHandle);
147   }
148 }
149