summaryrefslogtreecommitdiff
path: root/java/com/android/voicemail/impl/transcribe
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/voicemail/impl/transcribe')
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionBackfillService.java85
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java32
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java44
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionService.java52
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionTask.java141
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java123
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionTaskSync.java69
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/GetTranscriptResponseAsync.java102
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java44
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClientFactory.java4
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java53
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseAsync.java53
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseSync.java43
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto91
14 files changed, 827 insertions, 109 deletions
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionBackfillService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionBackfillService.java
new file mode 100644
index 000000000..f3c6e64f4
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionBackfillService.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 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.voicemail.impl.transcribe;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.app.job.JobWorkItem;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.annotation.WorkerThread;
+import android.support.v4.app.JobIntentService;
+import android.support.v4.os.BuildCompat;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.constants.ScheduledJobIds;
+import java.util.List;
+
+/**
+ * JobScheduler service for transcribing old voicemails. This service does a database scan for
+ * un-transcribed voicemails and schedules transcription tasks for them, once we have an un-metered
+ * network connection.
+ */
+public class TranscriptionBackfillService extends JobIntentService {
+
+ /** Schedule a task to scan the database for untranscribed voicemails */
+ public static boolean scheduleTask(Context context) {
+ if (BuildCompat.isAtLeastO()) {
+ LogUtil.enterBlock("TranscriptionBackfillService.transcribeOldVoicemails");
+ ComponentName componentName = new ComponentName(context, TranscriptionBackfillService.class);
+ JobInfo.Builder builder =
+ new JobInfo.Builder(ScheduledJobIds.VVM_TRANSCRIPTION_BACKFILL_JOB, componentName)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+ JobScheduler scheduler = context.getSystemService(JobScheduler.class);
+ return scheduler.enqueue(builder.build(), makeWorkItem()) == JobScheduler.RESULT_SUCCESS;
+ } else {
+ LogUtil.i("TranscriptionBackfillService.transcribeOldVoicemails", "not supported");
+ return false;
+ }
+ }
+
+ private static JobWorkItem makeWorkItem() {
+ Intent intent = new Intent();
+ return new JobWorkItem(intent);
+ }
+
+ @Override
+ @WorkerThread
+ protected void onHandleWork(Intent intent) {
+ LogUtil.enterBlock("TranscriptionBackfillService.onHandleWork");
+
+ TranscriptionDbHelper dbHelper = new TranscriptionDbHelper(this);
+ List<Uri> untranscribed = dbHelper.getUntranscribedVoicemails();
+ LogUtil.i(
+ "TranscriptionBackfillService.onHandleWork",
+ "found " + untranscribed.size() + " untranscribed voicemails");
+ // TODO(mdooley): Consider doing the actual transcriptions here instead of scheduling jobs.
+ for (Uri uri : untranscribed) {
+ ThreadUtil.postOnUiThread(
+ () -> {
+ TranscriptionService.scheduleNewVoicemailTranscriptionJob(this, uri, false);
+ });
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ LogUtil.enterBlock("TranscriptionBackfillService.onDestroy");
+ super.onDestroy();
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java b/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java
index 0c83615b4..83f04dab8 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java
@@ -53,10 +53,38 @@ public class TranscriptionConfigProvider {
.getBoolean("voicemail_transcription_server_use_plaintext", false);
}
+ public boolean shouldUseSyncApi() {
+ return ConfigProviderBindings.get(context)
+ .getBoolean("voicemail_transcription_server_use_sync_api", false);
+ }
+
+ public long getMaxTranscriptionRetries() {
+ return ConfigProviderBindings.get(context)
+ .getLong("voicemail_transcription_max_transcription_retries", 2L);
+ }
+
+ public long getMaxGetTranscriptPolls() {
+ return ConfigProviderBindings.get(context)
+ .getLong("voicemail_transcription_max_get_transcript_polls", 20L);
+ }
+
+ public long getGetTranscriptPollIntervalMillis() {
+ return ConfigProviderBindings.get(context)
+ .getLong("voicemail_transcription_get_transcript_poll_interval_millis", 1000L);
+ }
+
@Override
public String toString() {
return String.format(
- "{ address: %s, api key: %s, auth token: %s, plaintext: %b }",
- getServerAddress(), getApiKey(), getAuthToken(), shouldUsePlaintext());
+ "{ address: %s, api key: %s, auth token: %s, plaintext: %b, sync: %b, retries: %d, polls:"
+ + " %d, poll ms: %d }",
+ getServerAddress(),
+ getApiKey(),
+ getAuthToken(),
+ shouldUsePlaintext(),
+ shouldUseSyncApi(),
+ getMaxTranscriptionRetries(),
+ getMaxGetTranscriptPolls(),
+ getGetTranscriptPollIntervalMillis());
}
}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java b/java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java
index cbc5cb8a0..9d3c2e4a7 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java
@@ -17,29 +17,36 @@ package com.android.voicemail.impl.transcribe;
import android.annotation.TargetApi;
import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
import android.provider.VoicemailContract.Voicemails;
+import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.support.v4.os.BuildCompat;
import android.util.Pair;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
+import java.util.ArrayList;
+import java.util.List;
/** Helper class for reading and writing transcription data in the database */
@TargetApi(VERSION_CODES.O)
public class TranscriptionDbHelper {
- private static final String[] PROJECTION =
+ @VisibleForTesting
+ static final String[] PROJECTION =
new String[] {
- Voicemails.TRANSCRIPTION, // 0
- VoicemailCompat.TRANSCRIPTION_STATE // 1
+ Voicemails._ID, // 0
+ Voicemails.TRANSCRIPTION, // 1
+ VoicemailCompat.TRANSCRIPTION_STATE // 2
};
- public static final int TRANSCRIPTION = 0;
- public static final int TRANSCRIPTION_STATE = 1;
+ static final int ID = 0;
+ static final int TRANSCRIPTION = 1;
+ static final int TRANSCRIPTION_STATE = 2;
private final ContentResolver contentResolver;
private final Uri uri;
@@ -50,10 +57,14 @@ public class TranscriptionDbHelper {
this.uri = uri;
}
+ TranscriptionDbHelper(Context context) {
+ this(context, Voicemails.buildSourceUri(context.getPackageName()));
+ }
+
@WorkerThread
@TargetApi(VERSION_CODES.M) // used for try with resources
Pair<String, Integer> getTranscriptionAndState() {
- Assert.checkArgument(BuildCompat.isAtLeastO());
+ Assert.checkState(BuildCompat.isAtLeastO());
Assert.isWorkerThread();
try (Cursor cursor = contentResolver.query(uri, PROJECTION, null, null, null)) {
if (cursor == null) {
@@ -72,6 +83,27 @@ public class TranscriptionDbHelper {
}
@WorkerThread
+ @TargetApi(VERSION_CODES.M) // used for try with resources
+ List<Uri> getUntranscribedVoicemails() {
+ Assert.checkArgument(BuildCompat.isAtLeastO());
+ Assert.isWorkerThread();
+ List<Uri> untranscribed = new ArrayList<>();
+ String whereClause =
+ Voicemails.TRANSCRIPTION + " is NULL AND " + VoicemailCompat.TRANSCRIPTION_STATE + "=?";
+ String[] whereArgs = {String.valueOf(VoicemailCompat.TRANSCRIPTION_NOT_STARTED)};
+ try (Cursor cursor = contentResolver.query(uri, PROJECTION, whereClause, whereArgs, null)) {
+ if (cursor == null) {
+ LogUtil.e("TranscriptionDbHelper.getUntranscribedVoicemails", "query failed.");
+ } else {
+ while (cursor.moveToNext()) {
+ untranscribed.add(ContentUris.withAppendedId(uri, cursor.getLong(ID)));
+ }
+ }
+ }
+ return untranscribed;
+ }
+
+ @WorkerThread
void setTranscriptionState(int transcriptionState) {
Assert.isWorkerThread();
LogUtil.i(
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
index 3e80a7f59..2ca16fbf2 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
@@ -24,7 +24,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.os.StrictMode;
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
import android.support.v4.os.BuildCompat;
@@ -32,6 +31,8 @@ import android.text.TextUtils;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.constants.ScheduledJobIds;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -48,7 +49,6 @@ public class TranscriptionService extends JobService {
private JobParameters jobParameters;
private TranscriptionClientFactory clientFactory;
private TranscriptionConfigProvider configProvider;
- private StrictMode.VmPolicy originalPolicy;
/** Callback used by a task to indicate it has finished processing its work item */
interface JobCallback {
@@ -57,25 +57,37 @@ public class TranscriptionService extends JobService {
// Schedule a task to transcribe the indicated voicemail, return true if transcription task was
// scheduled.
- public static boolean transcribeVoicemail(Context context, Uri voicemailUri) {
+ @MainThread
+ public static boolean scheduleNewVoicemailTranscriptionJob(
+ Context context, Uri voicemailUri, boolean highPriority) {
Assert.isMainThread();
if (BuildCompat.isAtLeastO()) {
- LogUtil.i("TranscriptionService.transcribeVoicemail", "scheduling transcription");
+ LogUtil.i(
+ "TranscriptionService.scheduleNewVoicemailTranscriptionJob", "scheduling transcription");
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_VOICEMAIL_RECEIVED);
+
ComponentName componentName = new ComponentName(context, TranscriptionService.class);
JobInfo.Builder builder =
- new JobInfo.Builder(ScheduledJobIds.VVM_TRANSCRIPTION_JOB, componentName)
- .setMinimumLatency(0)
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ new JobInfo.Builder(ScheduledJobIds.VVM_TRANSCRIPTION_JOB, componentName);
+ if (highPriority) {
+ builder
+ .setMinimumLatency(0)
+ .setOverrideDeadline(0)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ } else {
+ builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+ }
JobScheduler scheduler = context.getSystemService(JobScheduler.class);
JobWorkItem workItem = makeWorkItem(voicemailUri);
return scheduler.enqueue(builder.build(), workItem) == JobScheduler.RESULT_SUCCESS;
} else {
- LogUtil.i("TranscriptionService.transcribeVoicemail", "not supported");
+ LogUtil.i("TranscriptionService.scheduleNewVoicemailTranscriptionJob", "not supported");
return false;
}
}
// Cancel all transcription tasks
+ @MainThread
public static void cancelTranscriptions(Context context) {
Assert.isMainThread();
LogUtil.enterBlock("TranscriptionService.cancelTranscriptions");
@@ -83,6 +95,7 @@ public class TranscriptionService extends JobService {
scheduler.cancel(ScheduledJobIds.VVM_TRANSCRIPTION_JOB);
}
+ @MainThread
public TranscriptionService() {
Assert.isMainThread();
}
@@ -98,6 +111,7 @@ public class TranscriptionService extends JobService {
}
@Override
+ @MainThread
public boolean onStartJob(JobParameters params) {
Assert.isMainThread();
LogUtil.enterBlock("TranscriptionService.onStartJob");
@@ -111,14 +125,13 @@ public class TranscriptionService extends JobService {
LogUtil.i(
"TranscriptionService.onStartJob",
"transcription server address: " + configProvider.getServerAddress());
- originalPolicy = StrictMode.getVmPolicy();
- StrictMode.enableDefaults();
jobParameters = params;
return checkForWork();
}
}
@Override
+ @MainThread
public boolean onStopJob(JobParameters params) {
Assert.isMainThread();
LogUtil.enterBlock("TranscriptionService.onStopJob");
@@ -127,6 +140,7 @@ public class TranscriptionService extends JobService {
}
@Override
+ @MainThread
public void onDestroy() {
Assert.isMainThread();
LogUtil.enterBlock("TranscriptionService.onDestroy");
@@ -142,10 +156,6 @@ public class TranscriptionService extends JobService {
executorService.shutdownNow();
executorService = null;
}
- if (originalPolicy != null) {
- StrictMode.setVmPolicy(originalPolicy);
- originalPolicy = null;
- }
}
@MainThread
@@ -153,14 +163,23 @@ public class TranscriptionService extends JobService {
Assert.isMainThread();
JobWorkItem workItem = jobParameters.dequeueWork();
if (workItem != null) {
- getExecutorService()
- .execute(new TranscriptionTask(this, new Callback(), workItem, getClientFactory()));
+ TranscriptionTask task =
+ configProvider.shouldUseSyncApi()
+ ? new TranscriptionTaskSync(
+ this, new Callback(), workItem, getClientFactory(), configProvider)
+ : new TranscriptionTaskAsync(
+ this, new Callback(), workItem, getClientFactory(), configProvider);
+ getExecutorService().execute(task);
return true;
} else {
return false;
}
}
+ static Uri getVoicemailUri(JobWorkItem workItem) {
+ return workItem.getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
+ }
+
private ExecutorService getExecutorService() {
if (executorService == null) {
// The common use case is transcribing a single voicemail so just use a single thread executor
@@ -173,6 +192,7 @@ public class TranscriptionService extends JobService {
private class Callback implements JobCallback {
@Override
+ @MainThread
public void onWorkCompleted(JobWorkItem completedWorkItem) {
Assert.isMainThread();
LogUtil.i("TranscriptionService.Callback.onWorkCompleted", completedWorkItem.toString());
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
index a14b6df91..b5f29da00 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
@@ -20,6 +20,7 @@ import android.app.job.JobWorkItem;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
+import android.util.Pair;
import com.android.dialer.common.concurrent.ThreadUtil;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
@@ -27,10 +28,10 @@ import com.android.voicemail.impl.VvmLog;
import com.android.voicemail.impl.transcribe.TranscriptionService.JobCallback;
import com.android.voicemail.impl.transcribe.grpc.TranscriptionClient;
import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
+import com.android.voicemail.impl.transcribe.grpc.TranscriptionResponse;
import com.google.internal.communications.voicemailtranscription.v1.AudioFormat;
-import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailRequest;
+import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus;
import com.google.protobuf.ByteString;
-import io.grpc.Status;
import java.io.IOException;
import java.io.InputStream;
@@ -41,7 +42,7 @@ import java.io.InputStream;
* This task performs the following steps:
* 1. Update the transcription-state in the database to 'in-progress'
* 2. Create grpc client and transcription request
- * 3. Make synchronous grpc transcription request to backend server
+ * 3. Make synchronous or asynchronous grpc transcription request to backend server
* 3a. On response
* Update the database with transcription (if successful) and new transcription-state
* 3b. On network error
@@ -50,7 +51,7 @@ import java.io.InputStream;
* 4. Notify the callback that the work item is complete
* </pre>
*/
-public class TranscriptionTask implements Runnable {
+public abstract class TranscriptionTask implements Runnable {
private static final String TAG = "TranscriptionTask";
private final Context context;
@@ -59,22 +60,29 @@ public class TranscriptionTask implements Runnable {
private final TranscriptionClientFactory clientFactory;
private final Uri voicemailUri;
private final TranscriptionDbHelper databaseHelper;
- private ByteString audioData;
- private AudioFormat encoding;
+ protected final TranscriptionConfigProvider configProvider;
+ protected ByteString audioData;
+ protected AudioFormat encoding;
- private static final int MAX_RETRIES = 2;
static final String AMR_PREFIX = "#!AMR\n";
- public TranscriptionTask(
+ /** Functional interface for sending requests to the transcription server */
+ public interface Request {
+ TranscriptionResponse getResponse(TranscriptionClient client);
+ }
+
+ TranscriptionTask(
Context context,
JobCallback callback,
JobWorkItem workItem,
- TranscriptionClientFactory clientFactory) {
+ TranscriptionClientFactory clientFactory,
+ TranscriptionConfigProvider configProvider) {
this.context = context;
this.callback = callback;
this.workItem = workItem;
this.clientFactory = clientFactory;
- this.voicemailUri = getVoicemailUri(workItem);
+ this.voicemailUri = TranscriptionService.getVoicemailUri(workItem);
+ this.configProvider = configProvider;
databaseHelper = new TranscriptionDbHelper(context, voicemailUri);
}
@@ -85,6 +93,13 @@ public class TranscriptionTask implements Runnable {
updateTranscriptionState(VoicemailCompat.TRANSCRIPTION_IN_PROGRESS);
transcribeVoicemail();
} else {
+ if (AudioFormat.AUDIO_FORMAT_UNSPECIFIED.equals(encoding)) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_VOICEMAIL_FORMAT_NOT_SUPPORTED);
+ } else {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_VOICEMAIL_INVALID_DATA);
+ }
updateTranscriptionState(VoicemailCompat.TRANSCRIPTION_FAILED);
}
ThreadUtil.postOnUiThread(
@@ -93,62 +108,77 @@ public class TranscriptionTask implements Runnable {
});
}
+ protected abstract Pair<String, TranscriptionStatus> getTranscription();
+
+ protected abstract DialerImpression.Type getRequestSentImpression();
+
private void transcribeVoicemail() {
VvmLog.i(TAG, "transcribeVoicemail");
- TranscribeVoicemailRequest request = makeRequest();
- TranscriptionClient client = clientFactory.getClient();
- String transcript = null;
- for (int i = 0; transcript == null && i < MAX_RETRIES; i++) {
- VvmLog.i(TAG, "transcribeVoicemail, try: " + (i + 1));
- if (i == 0) {
- Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_REQUEST_SENT);
- } else {
- Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_REQUEST_RETRY);
- }
- TranscriptionClient.TranscriptionResponseWrapper responseWrapper =
- client.transcribeVoicemail(request);
- if (responseWrapper.status != null) {
- VvmLog.i(TAG, "transcribeVoicemail, status: " + responseWrapper.status.getCode());
- if (shouldRetryRequest(responseWrapper.status)) {
+ Pair<String, TranscriptionStatus> pair = getTranscription();
+ String transcript = pair.first;
+ TranscriptionStatus status = pair.second;
+ if (!TextUtils.isEmpty(transcript)) {
+ updateTranscriptionAndState(transcript, VoicemailCompat.TRANSCRIPTION_AVAILABLE);
+ VvmLog.i(TAG, "transcribeVoicemail, got response");
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_SUCCESS);
+ } else {
+ VvmLog.i(TAG, "transcribeVoicemail, transcription unsuccessful, " + status);
+ switch (status) {
+ case FAILED_LANGUAGE_NOT_SUPPORTED:
Logger.get(context)
- .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_RECOVERABLE_ERROR);
- backoff(i);
- } else {
+ .logImpression(
+ DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_LANGUAGE_NOT_SUPPORTED);
+ break;
+ case FAILED_NO_SPEECH_DETECTED:
Logger.get(context)
- .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_FATAL_ERROR);
+ .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_NO_SPEECH_DETECTED);
break;
- }
- } else if (responseWrapper.response != null) {
- if (!TextUtils.isEmpty(responseWrapper.response.getTranscript())) {
- VvmLog.i(TAG, "transcribeVoicemail, got response");
- transcript = responseWrapper.response.getTranscript();
+ case EXPIRED:
Logger.get(context)
- .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_SUCCESS);
- } else {
- VvmLog.i(TAG, "transcribeVoicemail, empty transcription");
+ .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_EXPIRED);
+ break;
+ default:
Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_EMPTY);
- }
- } else {
- VvmLog.w(TAG, "transcribeVoicemail, no response");
- Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_INVALID);
+ break;
}
+ updateTranscriptionAndState(transcript, VoicemailCompat.TRANSCRIPTION_FAILED);
}
-
- int newState =
- (transcript == null)
- ? VoicemailCompat.TRANSCRIPTION_FAILED
- : VoicemailCompat.TRANSCRIPTION_AVAILABLE;
- updateTranscriptionAndState(transcript, newState);
}
- private static boolean shouldRetryRequest(Status status) {
- return status.getCode() == Status.Code.UNAVAILABLE;
+ protected TranscriptionResponse sendRequest(Request request) {
+ VvmLog.i(TAG, "sendRequest");
+ TranscriptionClient client = clientFactory.getClient();
+ for (int i = 0; i < configProvider.getMaxTranscriptionRetries(); i++) {
+ VvmLog.i(TAG, "sendRequest, try: " + (i + 1));
+ if (i == 0) {
+ Logger.get(context).logImpression(getRequestSentImpression());
+ } else {
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_REQUEST_RETRY);
+ }
+
+ TranscriptionResponse response = request.getResponse(client);
+ if (response.hasRecoverableError()) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_RECOVERABLE_ERROR);
+ backoff(i);
+ } else {
+ return response;
+ }
+ }
+
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_TOO_MANY_ERRORS);
+ return null;
}
private static void backoff(int retryCount) {
VvmLog.i(TAG, "backoff, count: " + retryCount);
+ long millis = (1L << retryCount) * 1000;
+ sleep(millis);
+ }
+
+ protected static void sleep(long millis) {
try {
- long millis = (1 << retryCount) * 1000;
Thread.sleep(millis);
} catch (InterruptedException e) {
VvmLog.w(TAG, "interrupted");
@@ -164,13 +194,6 @@ public class TranscriptionTask implements Runnable {
databaseHelper.setTranscriptionState(newState);
}
- private TranscribeVoicemailRequest makeRequest() {
- return TranscribeVoicemailRequest.newBuilder()
- .setVoicemailData(audioData)
- .setAudioFormat(encoding)
- .build();
- }
-
// Uses try-with-resource
@TargetApi(android.os.Build.VERSION_CODES.M)
private boolean readAndValidateAudioFile() {
@@ -199,8 +222,4 @@ public class TranscriptionTask implements Runnable {
return true;
}
-
- private static Uri getVoicemailUri(JobWorkItem workItem) {
- return workItem.getIntent().getParcelableExtra(TranscriptionService.EXTRA_VOICEMAIL_URI);
- }
}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
new file mode 100644
index 000000000..3c41aef89
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 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.voicemail.impl.transcribe;
+
+import android.app.job.JobWorkItem;
+import android.content.Context;
+import android.util.Pair;
+import com.android.dialer.common.Assert;
+import com.android.dialer.logging.DialerImpression;
+import com.android.voicemail.impl.VvmLog;
+import com.android.voicemail.impl.transcribe.TranscriptionService.JobCallback;
+import com.android.voicemail.impl.transcribe.grpc.GetTranscriptResponseAsync;
+import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
+import com.android.voicemail.impl.transcribe.grpc.TranscriptionResponseAsync;
+import com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest;
+import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest;
+import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus;
+
+/**
+ * Background task to get a voicemail transcription using the asynchronous API. The async API works
+ * as follows:
+ *
+ * <ol>
+ * <li>client uploads voicemail data to the server
+ * <li>server responds with a transcription-id and an estimated transcription wait time
+ * <li>client waits appropriate amount of time then begins polling for the result
+ * </ol>
+ *
+ * This implementation blocks until the response or an error is received, even though it is using
+ * the asynchronous server API.
+ */
+public class TranscriptionTaskAsync extends TranscriptionTask {
+ private static final String TAG = "TranscriptionTaskAsync";
+
+ public TranscriptionTaskAsync(
+ Context context,
+ JobCallback callback,
+ JobWorkItem workItem,
+ TranscriptionClientFactory clientFactory,
+ TranscriptionConfigProvider configProvider) {
+ super(context, callback, workItem, clientFactory, configProvider);
+ }
+
+ @Override
+ protected Pair<String, TranscriptionStatus> getTranscription() {
+ VvmLog.i(TAG, "getTranscription");
+
+ TranscriptionResponseAsync uploadResponse =
+ (TranscriptionResponseAsync)
+ sendRequest((client) -> client.sendUploadRequest(getUploadRequest()));
+
+ if (uploadResponse == null) {
+ VvmLog.i(TAG, "getTranscription, failed to upload voicemail.");
+ return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
+ } else {
+ waitForTranscription(uploadResponse);
+ return pollForTranscription(uploadResponse);
+ }
+ }
+
+ @Override
+ protected DialerImpression.Type getRequestSentImpression() {
+ return DialerImpression.Type.VVM_TRANSCRIPTION_REQUEST_SENT_ASYNC;
+ }
+
+ private static void waitForTranscription(TranscriptionResponseAsync uploadResponse) {
+ long millis = uploadResponse.getEstimatedWaitMillis();
+ VvmLog.i(TAG, "waitForTranscription, " + millis + " millis");
+ sleep(millis);
+ }
+
+ private Pair<String, TranscriptionStatus> pollForTranscription(
+ TranscriptionResponseAsync uploadResponse) {
+ VvmLog.i(TAG, "pollForTranscription");
+ GetTranscriptRequest request = getGetTranscriptRequest(uploadResponse);
+ for (int i = 0; i < configProvider.getMaxGetTranscriptPolls(); i++) {
+ GetTranscriptResponseAsync response =
+ (GetTranscriptResponseAsync)
+ sendRequest((client) -> client.sendGetTranscriptRequest(request));
+ if (response == null) {
+ VvmLog.i(TAG, "pollForTranscription, no transcription result.");
+ } else if (response.isTranscribing()) {
+ VvmLog.i(TAG, "pollForTranscription, poll count: " + (i + 1));
+ } else if (response.hasFatalError()) {
+ VvmLog.i(TAG, "pollForTranscription, fail. " + response.getErrorDescription());
+ return new Pair<>(null, response.getTranscriptionStatus());
+ } else {
+ VvmLog.i(TAG, "pollForTranscription, got transcription");
+ return new Pair<>(response.getTranscript(), TranscriptionStatus.SUCCESS);
+ }
+ sleep(configProvider.getGetTranscriptPollIntervalMillis());
+ }
+ VvmLog.i(TAG, "pollForTranscription, timed out.");
+ return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
+ }
+
+ private TranscribeVoicemailAsyncRequest getUploadRequest() {
+ return TranscribeVoicemailAsyncRequest.newBuilder()
+ .setVoicemailData(audioData)
+ .setAudioFormat(encoding)
+ .build();
+ }
+
+ private GetTranscriptRequest getGetTranscriptRequest(TranscriptionResponseAsync uploadResponse) {
+ Assert.checkArgument(uploadResponse.getTranscriptionId() != null);
+ return GetTranscriptRequest.newBuilder()
+ .setTranscriptionId(uploadResponse.getTranscriptionId())
+ .build();
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskSync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskSync.java
new file mode 100644
index 000000000..bee68590a
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskSync.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 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.voicemail.impl.transcribe;
+
+import android.app.job.JobWorkItem;
+import android.content.Context;
+import android.util.Pair;
+import com.android.dialer.logging.DialerImpression;
+import com.android.voicemail.impl.VvmLog;
+import com.android.voicemail.impl.transcribe.TranscriptionService.JobCallback;
+import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
+import com.android.voicemail.impl.transcribe.grpc.TranscriptionResponseSync;
+import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailRequest;
+import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus;
+
+/** Background task to get a voicemail transcription using the synchronous API */
+public class TranscriptionTaskSync extends TranscriptionTask {
+ private static final String TAG = "TranscriptionTaskSync";
+
+ public TranscriptionTaskSync(
+ Context context,
+ JobCallback callback,
+ JobWorkItem workItem,
+ TranscriptionClientFactory clientFactory,
+ TranscriptionConfigProvider configProvider) {
+ super(context, callback, workItem, clientFactory, configProvider);
+ }
+
+ @Override
+ protected Pair<String, TranscriptionStatus> getTranscription() {
+ VvmLog.i(TAG, "getTranscription");
+
+ TranscriptionResponseSync response =
+ (TranscriptionResponseSync)
+ sendRequest((client) -> client.sendSyncRequest(getSyncRequest()));
+ if (response == null) {
+ VvmLog.i(TAG, "getTranscription, failed to transcribe voicemail.");
+ return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
+ } else {
+ VvmLog.i(TAG, "getTranscription, got transcription");
+ return new Pair<>(response.getTranscript(), TranscriptionStatus.SUCCESS);
+ }
+ }
+
+ @Override
+ protected DialerImpression.Type getRequestSentImpression() {
+ return DialerImpression.Type.VVM_TRANSCRIPTION_REQUEST_SENT;
+ }
+
+ private TranscribeVoicemailRequest getSyncRequest() {
+ return TranscribeVoicemailRequest.newBuilder()
+ .setVoicemailData(audioData)
+ .setAudioFormat(encoding)
+ .build();
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/GetTranscriptResponseAsync.java b/java/com/android/voicemail/impl/transcribe/grpc/GetTranscriptResponseAsync.java
new file mode 100644
index 000000000..f979d69ce
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/grpc/GetTranscriptResponseAsync.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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.voicemail.impl.transcribe.grpc;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import com.android.dialer.common.Assert;
+import com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse;
+import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus;
+import io.grpc.Status;
+
+/** Container for response and status objects for an asynchronous get-transcript request */
+public class GetTranscriptResponseAsync extends TranscriptionResponse {
+ @Nullable private final GetTranscriptResponse response;
+
+ @VisibleForTesting
+ public GetTranscriptResponseAsync(GetTranscriptResponse response) {
+ Assert.checkArgument(response != null);
+ this.response = response;
+ }
+
+ @VisibleForTesting
+ public GetTranscriptResponseAsync(Status status) {
+ super(status);
+ this.response = null;
+ }
+
+ public @Nullable String getTranscript() {
+ if (response != null) {
+ return response.getTranscript();
+ }
+ return null;
+ }
+
+ public @Nullable String getErrorDescription() {
+ if (!hasRecoverableError() && !hasFatalError()) {
+ return null;
+ }
+ if (status != null) {
+ return "Grpc error: " + status;
+ }
+ if (response != null) {
+ return "Transcription error: " + response.getStatus();
+ }
+ Assert.fail("Impossible state");
+ return null;
+ }
+
+ public TranscriptionStatus getTranscriptionStatus() {
+ if (response == null) {
+ return TranscriptionStatus.TRANSCRIPTION_STATUS_UNSPECIFIED;
+ } else {
+ return response.getStatus();
+ }
+ }
+
+ public boolean isTranscribing() {
+ return response != null && response.getStatus() == TranscriptionStatus.PENDING;
+ }
+
+ @Override
+ public boolean hasRecoverableError() {
+ if (super.hasRecoverableError()) {
+ return true;
+ }
+
+ if (response != null) {
+ return response.getStatus() == TranscriptionStatus.EXPIRED
+ || response.getStatus() == TranscriptionStatus.FAILED_RETRY;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean hasFatalError() {
+ if (super.hasFatalError()) {
+ return true;
+ }
+
+ if (response != null) {
+ return response.getStatus() == TranscriptionStatus.FAILED_NO_RETRY
+ || response.getStatus() == TranscriptionStatus.FAILED_LANGUAGE_NOT_SUPPORTED
+ || response.getStatus() == TranscriptionStatus.FAILED_NO_SPEECH_DETECTED;
+ }
+
+ return false;
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java
index 27603d910..b18d95627 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java
@@ -15,13 +15,11 @@
*/
package com.android.voicemail.impl.transcribe.grpc;
-import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
-import com.android.dialer.common.Assert;
+import com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest;
+import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest;
import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailRequest;
-import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailResponse;
import com.google.internal.communications.voicemailtranscription.v1.VoicemailTranscriptionServiceGrpc;
-import io.grpc.Status;
import io.grpc.StatusRuntimeException;
/** Wrapper around Grpc transcription server stub */
@@ -29,33 +27,35 @@ public class TranscriptionClient {
private final VoicemailTranscriptionServiceGrpc.VoicemailTranscriptionServiceBlockingStub stub;
- /** Wraps the server response and status objects, either of which may be null. */
- public static class TranscriptionResponseWrapper {
- public final TranscribeVoicemailResponse response;
- public final Status status;
+ TranscriptionClient(
+ VoicemailTranscriptionServiceGrpc.VoicemailTranscriptionServiceBlockingStub stub) {
+ this.stub = stub;
+ }
- public TranscriptionResponseWrapper(
- @Nullable TranscribeVoicemailResponse response, @Nullable Status status) {
- Assert.checkArgument(!(response == null && status == null));
- this.response = response;
- this.status = status;
+ @WorkerThread
+ public TranscriptionResponseSync sendSyncRequest(TranscribeVoicemailRequest request) {
+ try {
+ return new TranscriptionResponseSync(stub.transcribeVoicemail(request));
+ } catch (StatusRuntimeException e) {
+ return new TranscriptionResponseSync(e.getStatus());
}
}
- TranscriptionClient(
- VoicemailTranscriptionServiceGrpc.VoicemailTranscriptionServiceBlockingStub stub) {
- this.stub = stub;
+ @WorkerThread
+ public TranscriptionResponseAsync sendUploadRequest(TranscribeVoicemailAsyncRequest request) {
+ try {
+ return new TranscriptionResponseAsync(stub.transcribeVoicemailAsync(request));
+ } catch (StatusRuntimeException e) {
+ return new TranscriptionResponseAsync(e.getStatus());
+ }
}
@WorkerThread
- public TranscriptionResponseWrapper transcribeVoicemail(TranscribeVoicemailRequest request) {
- TranscribeVoicemailResponse response = null;
- Status status = null;
+ public GetTranscriptResponseAsync sendGetTranscriptRequest(GetTranscriptRequest request) {
try {
- response = stub.transcribeVoicemail(request);
+ return new GetTranscriptResponseAsync(stub.getTranscript(request));
} catch (StatusRuntimeException e) {
- status = e.getStatus();
+ return new GetTranscriptResponseAsync(e.getStatus());
}
- return new TranscriptionClient.TranscriptionResponseWrapper(response, status);
}
}
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClientFactory.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClientFactory.java
index 6101ed516..c57b01fd7 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClientFactory.java
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClientFactory.java
@@ -76,7 +76,9 @@ public class TranscriptionClientFactory {
public void shutdown() {
LogUtil.enterBlock("TranscriptionClientFactory.shutdown");
- originalChannel.shutdown();
+ if (!originalChannel.isShutdown()) {
+ originalChannel.shutdown();
+ }
}
private static ManagedChannel getManagedChannel(TranscriptionConfigProvider configProvider) {
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java
new file mode 100644
index 000000000..f0823de32
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 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.voicemail.impl.transcribe.grpc;
+
+import android.support.annotation.Nullable;
+import com.android.dialer.common.Assert;
+import io.grpc.Status;
+
+/**
+ * Base class for encapulating a voicemail transcription server response. This handles the Grpc
+ * status response, subclasses will handle request specific responses.
+ */
+public abstract class TranscriptionResponse {
+ @Nullable public final Status status;
+
+ TranscriptionResponse() {
+ this.status = null;
+ }
+
+ TranscriptionResponse(Status status) {
+ Assert.checkArgument(status != null);
+ this.status = status;
+ }
+
+ public boolean hasRecoverableError() {
+ if (status != null) {
+ return status.getCode() == Status.Code.UNAVAILABLE;
+ }
+
+ return false;
+ }
+
+ public boolean hasFatalError() {
+ if (status != null) {
+ return status.getCode() != Status.Code.OK && status.getCode() != Status.Code.UNAVAILABLE;
+ }
+
+ return false;
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseAsync.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseAsync.java
new file mode 100644
index 000000000..38b463053
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseAsync.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 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.voicemail.impl.transcribe.grpc;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import com.android.dialer.common.Assert;
+import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse;
+import io.grpc.Status;
+
+/** Container for response and status objects for an asynchronous transcription upload request */
+public class TranscriptionResponseAsync extends TranscriptionResponse {
+ @Nullable private final TranscribeVoicemailAsyncResponse response;
+
+ @VisibleForTesting
+ public TranscriptionResponseAsync(TranscribeVoicemailAsyncResponse response) {
+ Assert.checkArgument(response != null);
+ this.response = response;
+ }
+
+ @VisibleForTesting
+ public TranscriptionResponseAsync(Status status) {
+ super(status);
+ this.response = null;
+ }
+
+ public @Nullable String getTranscriptionId() {
+ if (response != null) {
+ return response.getTranscriptionId();
+ }
+ return null;
+ }
+
+ public long getEstimatedWaitMillis() {
+ if (response != null) {
+ return response.getEstimatedWaitSecs() * 1_000L;
+ }
+ return 0;
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseSync.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseSync.java
new file mode 100644
index 000000000..d2e2e218c
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseSync.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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.voicemail.impl.transcribe.grpc;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import com.android.dialer.common.Assert;
+import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailResponse;
+import io.grpc.Status;
+
+/** Container for response and status objects for a synchronous transcription request */
+public class TranscriptionResponseSync extends TranscriptionResponse {
+ @Nullable private final TranscribeVoicemailResponse response;
+
+ @VisibleForTesting
+ public TranscriptionResponseSync(Status status) {
+ super(status);
+ this.response = null;
+ }
+
+ @VisibleForTesting
+ public TranscriptionResponseSync(TranscribeVoicemailResponse response) {
+ Assert.checkArgument(response != null);
+ this.response = response;
+ }
+
+ public @Nullable String getTranscript() {
+ return (response != null) ? response.getTranscript() : null;
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto b/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto
index 4b1e19b8a..a2064d193 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto
+++ b/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto
@@ -5,9 +5,10 @@ syntax = "proto2";
package google.internal.communications.voicemailtranscription.v1;
option java_multiple_files = true;
-option java_package = "com.google.internal.communications.voicemailtranscription.v1";
option optimize_for = LITE_RUNTIME;
+option java_package = "com.google.internal.communications.voicemailtranscription.v1";
+
// Enum that specifies supported audio formats.
enum AudioFormat {
// Default but invalid value.
@@ -18,6 +19,43 @@ enum AudioFormat {
AMR_NB_8KHZ = 1;
}
+// Enum that describes the status of the transcription process.
+enum TranscriptionStatus {
+ // Default but invalid value.
+ TRANSCRIPTION_STATUS_UNSPECIFIED = 0;
+
+ // Transcription was successful and the transcript is present.
+ SUCCESS = 1;
+
+ // Transcription is progress. Check again later.
+ PENDING = 2;
+
+ // Transcription was successful, but the expiration period has passed, which
+ // means that the sensative data (including the transcript) has been deleted.
+ // Resend the voicemail through TranscribeVoicemailAsync to retry.
+ EXPIRED = 3;
+
+ // Internal error encountered during the transcription.
+ // Resend the voicemail through TranscribeVoicemailAsync to retry.
+ // This is a catch-all status for all retriable errors that aren't captured by
+ // a more specfic status.
+ FAILED_RETRY = 4;
+
+ // Internal error encountered during the transcription.
+ // Do not resend the voicemail.
+ // This is a catch-all status for all non-retriable errors that aren't
+ // captured by a more specfic status.
+ FAILED_NO_RETRY = 5;
+
+ // The language detected is not yet supported by this service.
+ // Do not resend the voicemail.
+ FAILED_LANGUAGE_NOT_SUPPORTED = 6;
+
+ // No speech was detected in the voicemail.
+ // Do not resend the voicemail.
+ FAILED_NO_SPEECH_DETECTED = 7;
+}
+
// Request for synchronous voicemail transcription.
message TranscribeVoicemailRequest {
// Voicemail audio file containing the raw bytes we receive from the carrier.
@@ -33,11 +71,62 @@ message TranscribeVoicemailResponse {
optional string transcript = 1;
}
+// Request for asynchronous voicemail transcription.
+message TranscribeVoicemailAsyncRequest {
+ // Voicemail audio data encoded in the format specified by audio_format.
+ optional bytes voicemail_data = 1;
+
+ // Audio format of the voicemail file.
+ optional AudioFormat audio_format = 2;
+}
+
+// Response for asynchronous voicemail transcription containing information
+// needed to fetch the transcription results through the GetTranscript method.
+message TranscribeVoicemailAsyncResponse {
+ // Unique ID for the transcription. This ID is used for retrieving the
+ // voicemail transcript later.
+ optional string transcription_id = 1;
+
+ // The estimated amount of time in seconds before the transcription will be
+ // available.
+ // The client should not call GetTranscript until this time has elapsed, but
+ // the transcript is not guaranteed to be ready by this time.
+ optional int64 estimated_wait_secs = 2;
+}
+
+// Request for retrieving an asynchronously generated transcript.
+message GetTranscriptRequest {
+ // Unique ID for the transcription. This ID was returned by
+ // TranscribeVoicemailAsync.
+ optional string transcription_id = 1;
+}
+
+// Response for retrieving an asynchronously generated transcript.
+message GetTranscriptResponse {
+ // Status of the trascription process.
+ optional TranscriptionStatus status = 1;
+
+ // The transcribed text of the voicemail. This is only present if the status
+ // is SUCCESS.
+ optional string transcript = 2;
+}
+
// RPC service for transcribing voicemails.
service VoicemailTranscriptionService {
// Returns a transcript of the given voicemail.
rpc TranscribeVoicemail(TranscribeVoicemailRequest)
returns (TranscribeVoicemailResponse) {}
+
+ // Schedules a transcription of the given voicemail. The transcript can be
+ // retrieved using the returned ID.
+ rpc TranscribeVoicemailAsync(TranscribeVoicemailAsyncRequest)
+ returns (TranscribeVoicemailAsyncResponse) {
+ }
+
+ // Returns the transcript corresponding to the given ID, which was returned
+ // by TranscribeVoicemailAsync.
+ rpc GetTranscript(GetTranscriptRequest) returns (GetTranscriptResponse) {
+ }
}
// LINT.ThenChange(//depot/google3/google/internal/communications/voicemailtranscription/v1/\