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