diff options
author | Eric Erfanian <erfanian@google.com> | 2017-08-29 18:16:25 -0700 |
---|---|---|
committer | Eric Erfanian <erfanian@google.com> | 2017-08-29 18:16:25 -0700 |
commit | 483d957ef7f2c6976c2e4647c4da6258e861fb54 (patch) | |
tree | 4d88b77d23b595a47fad3dea9e1a3b514356c24e /java/com | |
parent | 939cdf008c2fb1cae41b056824e5e4642e2a41f5 (diff) |
Adding code to use the async voicemail transcription API
We have observed ~40% error rate with the synchronous transcription service and
suspect that it is at least partly due to the fact that the synchronous API requires that
we maintain a network connection to the server for up to several minutes.
This cl allows us to also use the asynchronous transcription API which will eliminate
the need for long lived network connections. The client will still block until
the transcription is complete but this will be on a background thread and shouldn't
affect performance.
Also adding more impression logging for the new async API errors.
Bug: 37340510
Test: manual and updated unit tests
PiperOrigin-RevId: 165320593
Change-Id: Icf0e0b38d3a584fa679968592f91db0c42a6f1ee
Diffstat (limited to 'java/com')
15 files changed, 832 insertions, 88 deletions
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto index de38ff359..b02c82b57 100644 --- a/java/com/android/dialer/logging/dialer_impression.proto +++ b/java/com/android/dialer/logging/dialer_impression.proto @@ -504,5 +504,17 @@ message DialerImpression { // notifications is very high and the system may suppress future // notifications. HIGH_GLOBAL_NOTIFICATION_COUNT_REACHED = 1249; + + // More impressions for interactions with the voicemail transcription server + VVM_TRANSCRIPTION_REQUEST_SENT_ASYNC = 1250; + VVM_TRANSCRIPTION_VOICEMAIL_RECEIVED = 1251; + VVM_TRANSCRIPTION_VOICEMAIL_FORMAT_NOT_SUPPORTED = 1252; + VVM_TRANSCRIPTION_VOICEMAIL_INVALID_DATA = 1253; + VVM_TRANSCRIPTION_VOICEMAIL_UPLOAD_FAILED = 1254; + VVM_TRANSCRIPTION_RESPONSE_LANGUAGE_NOT_SUPPORTED = 1255; + VVM_TRANSCRIPTION_RESPONSE_NO_SPEECH_DETECTED = 1256; + VVM_TRANSCRIPTION_RESPONSE_EXPIRED = 1257; + VVM_TRANSCRIPTION_RESPONSE_TOO_MANY_ERRORS = 1258; + VVM_TRANSCRIPTION_POLLING_TIMEOUT = 1259; } } diff --git a/java/com/android/incallui/autoresizetext/AutoResizeTextView.java b/java/com/android/incallui/autoresizetext/AutoResizeTextView.java index 8fd0b4868..5a22b93dc 100644 --- a/java/com/android/incallui/autoresizetext/AutoResizeTextView.java +++ b/java/com/android/incallui/autoresizetext/AutoResizeTextView.java @@ -226,10 +226,13 @@ public class AutoResizeTextView extends TextView { } else { // If multiline, lay the text out, then check the number of lines, the layout's height, // and each line's width. - StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, maxWidth) - .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) - .setUseLineSpacingFromFallbacks(true) - .build(); + StaticLayout layout = new StaticLayout(text, + textPaint, + maxWidth, + Alignment.ALIGN_NORMAL, + getLineSpacingMultiplier(), + getLineSpacingExtra(), + true); // Return false if we need more than maxLines. The text is obviously too big in this case. if (maxLines != NO_LINE_LIMIT && layout.getLineCount() > maxLines) { diff --git a/java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java b/java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java index 448c69356..8fcbf3b97 100644 --- a/java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java +++ b/java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java @@ -55,6 +55,24 @@ public class VoicemailTranscriptionServiceGrpc { "google.internal.communications.voicemailtranscription.v1.VoicemailTranscriptionService", "TranscribeVoicemail"), io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailRequest.getDefaultInstance()), io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailResponse.getDefaultInstance())); + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1901") + public static final io.grpc.MethodDescriptor<com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest, + com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse> METHOD_TRANSCRIBE_VOICEMAIL_ASYNC = + io.grpc.MethodDescriptor.create( + io.grpc.MethodDescriptor.MethodType.UNARY, + generateFullMethodName( + "google.internal.communications.voicemailtranscription.v1.VoicemailTranscriptionService", "TranscribeVoicemailAsync"), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest.getDefaultInstance()), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse.getDefaultInstance())); + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1901") + public static final io.grpc.MethodDescriptor<com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest, + com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse> METHOD_GET_TRANSCRIPT = + io.grpc.MethodDescriptor.create( + io.grpc.MethodDescriptor.MethodType.UNARY, + generateFullMethodName( + "google.internal.communications.voicemailtranscription.v1.VoicemailTranscriptionService", "GetTranscript"), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest.getDefaultInstance()), + io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse.getDefaultInstance())); /** * Creates a new async stub that supports all call types for the service @@ -96,6 +114,28 @@ public class VoicemailTranscriptionServiceGrpc { asyncUnimplementedUnaryCall(METHOD_TRANSCRIBE_VOICEMAIL, responseObserver); } + /** + * <pre> + * Schedules a transcription of the given voicemail. The transcript can be + * retrieved using the returned ID. + * </pre> + */ + public void transcribeVoicemailAsync(com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest request, + io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse> responseObserver) { + asyncUnimplementedUnaryCall(METHOD_TRANSCRIBE_VOICEMAIL_ASYNC, responseObserver); + } + + /** + * <pre> + * Returns the transcript corresponding to the given ID, which was returned + * by TranscribeVoicemailAsync. + * </pre> + */ + public void getTranscript(com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest request, + io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse> responseObserver) { + asyncUnimplementedUnaryCall(METHOD_GET_TRANSCRIPT, responseObserver); + } + @java.lang.Override public io.grpc.ServerServiceDefinition bindService() { return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) .addMethod( @@ -105,6 +145,20 @@ public class VoicemailTranscriptionServiceGrpc { com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailRequest, com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailResponse>( this, METHODID_TRANSCRIBE_VOICEMAIL))) + .addMethod( + METHOD_TRANSCRIBE_VOICEMAIL_ASYNC, + asyncUnaryCall( + new MethodHandlers< + com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest, + com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse>( + this, METHODID_TRANSCRIBE_VOICEMAIL_ASYNC))) + .addMethod( + METHOD_GET_TRANSCRIPT, + asyncUnaryCall( + new MethodHandlers< + com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest, + com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse>( + this, METHODID_GET_TRANSCRIPT))) .build(); } } @@ -140,6 +194,30 @@ public class VoicemailTranscriptionServiceGrpc { asyncUnaryCall( getChannel().newCall(METHOD_TRANSCRIBE_VOICEMAIL, getCallOptions()), request, responseObserver); } + + /** + * <pre> + * Schedules a transcription of the given voicemail. The transcript can be + * retrieved using the returned ID. + * </pre> + */ + public void transcribeVoicemailAsync(com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest request, + io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse> responseObserver) { + asyncUnaryCall( + getChannel().newCall(METHOD_TRANSCRIBE_VOICEMAIL_ASYNC, getCallOptions()), request, responseObserver); + } + + /** + * <pre> + * Returns the transcript corresponding to the given ID, which was returned + * by TranscribeVoicemailAsync. + * </pre> + */ + public void getTranscript(com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest request, + io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse> responseObserver) { + asyncUnaryCall( + getChannel().newCall(METHOD_GET_TRANSCRIPT, getCallOptions()), request, responseObserver); + } } /** @@ -172,6 +250,28 @@ public class VoicemailTranscriptionServiceGrpc { return blockingUnaryCall( getChannel(), METHOD_TRANSCRIBE_VOICEMAIL, getCallOptions(), request); } + + /** + * <pre> + * Schedules a transcription of the given voicemail. The transcript can be + * retrieved using the returned ID. + * </pre> + */ + public com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse transcribeVoicemailAsync(com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest request) { + return blockingUnaryCall( + getChannel(), METHOD_TRANSCRIBE_VOICEMAIL_ASYNC, getCallOptions(), request); + } + + /** + * <pre> + * Returns the transcript corresponding to the given ID, which was returned + * by TranscribeVoicemailAsync. + * </pre> + */ + public com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse getTranscript(com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest request) { + return blockingUnaryCall( + getChannel(), METHOD_GET_TRANSCRIPT, getCallOptions(), request); + } } /** @@ -205,9 +305,35 @@ public class VoicemailTranscriptionServiceGrpc { return futureUnaryCall( getChannel().newCall(METHOD_TRANSCRIBE_VOICEMAIL, getCallOptions()), request); } + + /** + * <pre> + * Schedules a transcription of the given voicemail. The transcript can be + * retrieved using the returned ID. + * </pre> + */ + public com.google.common.util.concurrent.ListenableFuture<com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse> transcribeVoicemailAsync( + com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest request) { + return futureUnaryCall( + getChannel().newCall(METHOD_TRANSCRIBE_VOICEMAIL_ASYNC, getCallOptions()), request); + } + + /** + * <pre> + * Returns the transcript corresponding to the given ID, which was returned + * by TranscribeVoicemailAsync. + * </pre> + */ + public com.google.common.util.concurrent.ListenableFuture<com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse> getTranscript( + com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest request) { + return futureUnaryCall( + getChannel().newCall(METHOD_GET_TRANSCRIPT, getCallOptions()), request); + } } private static final int METHODID_TRANSCRIBE_VOICEMAIL = 0; + private static final int METHODID_TRANSCRIBE_VOICEMAIL_ASYNC = 1; + private static final int METHODID_GET_TRANSCRIPT = 2; private static class MethodHandlers<Req, Resp> implements io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>, @@ -230,6 +356,14 @@ public class VoicemailTranscriptionServiceGrpc { serviceImpl.transcribeVoicemail((com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailRequest) request, (io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailResponse>) responseObserver); break; + case METHODID_TRANSCRIBE_VOICEMAIL_ASYNC: + serviceImpl.transcribeVoicemailAsync((com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest) request, + (io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncResponse>) responseObserver); + break; + case METHODID_GET_TRANSCRIPT: + serviceImpl.getTranscript((com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest) request, + (io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse>) responseObserver); + break; default: throw new AssertionError(); } @@ -248,7 +382,9 @@ public class VoicemailTranscriptionServiceGrpc { public static io.grpc.ServiceDescriptor getServiceDescriptor() { return new io.grpc.ServiceDescriptor(SERVICE_NAME, - METHOD_TRANSCRIBE_VOICEMAIL); + METHOD_TRANSCRIBE_VOICEMAIL, + METHOD_TRANSCRIBE_VOICEMAIL_ASYNC, + METHOD_GET_TRANSCRIPT); } } 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/TranscriptionService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java index 9a781e6c2..6933a3a66 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java @@ -31,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.dialer.strictmode.DialerStrictMode; import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory; import java.util.concurrent.ExecutorService; @@ -60,6 +62,8 @@ public class TranscriptionService extends JobService { Assert.isMainThread(); if (BuildCompat.isAtLeastO()) { LogUtil.i("TranscriptionService.transcribeVoicemail", "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) @@ -148,8 +152,13 @@ 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; diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java index a14b6df91..61a5ee8de 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.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() { 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 dd2170ade..a2064d193 100644 --- a/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto +++ b/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto @@ -19,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. @@ -34,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/\ |