diff options
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/\ |