summaryrefslogtreecommitdiff
path: root/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
diff options
context:
space:
mode:
authormdooley <mdooley@google.com>2017-11-28 19:54:52 -0800
committerCopybara-Service <copybara-piper@google.com>2017-11-29 11:49:57 -0800
commit00d18e0efe89d7bbd4c69de6988ef6f9a62df782 (patch)
tree750103032e4860ab2be3f540eb5993f45e133365 /java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
parent1306455ce5eb75c74790526ec3687293221a42ba (diff)
Switching to alarms and exponential backoff while polling for transcription result
This cl switches the way asynchronous transcription tasks poll for their results. it has been observed that sometimes the server side transcription takes longer than expected (sometimes many minutes), so instead of blocking a thread for all that time this cl schedules an alarm for the next time to poll. it also uses an exponential backoff scheme to determine the poll times and increases the maximum total polling time from 20 seconds to 20 minutes. Bug: 66966157 Test: manual and unit tests PiperOrigin-RevId: 177257577 Change-Id: Ib2998f03cc418d5241ccffec71ba3945c9fe4cbc
Diffstat (limited to 'java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java')
-rw-r--r--java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java233
1 files changed, 233 insertions, 0 deletions
diff --git a/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java b/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
new file mode 100644
index 000000000..cc204ff53
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
@@ -0,0 +1,233 @@
+/*
+ * 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.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.support.annotation.Nullable;
+import android.util.Pair;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.backoff.ExponentialBaseCalculator;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+import com.android.voicemail.impl.VvmLog;
+import com.android.voicemail.impl.transcribe.grpc.GetTranscriptResponseAsync;
+import com.android.voicemail.impl.transcribe.grpc.TranscriptionClient;
+import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
+import com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest;
+import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus;
+
+/**
+ * This class uses the AlarmManager to poll for the result of a voicemail transcription request.
+ * Initially it waits for the estimated transcription time, and if the result is not available then
+ * it polls using an exponential backoff scheme.
+ */
+public class GetTranscriptReceiver extends BroadcastReceiver {
+ private static final String TAG = "GetTranscriptReceiver";
+ static final String EXTRA_IS_INITIAL_ESTIMATED_WAIT = "extra_is_initial_estimated_wait";
+ static final String EXTRA_VOICEMAIL_URI = "extra_voicemail_uri";
+ static final String EXTRA_TRANSCRIPT_ID = "extra_transcript_id";
+ static final String EXTRA_DELAY_MILLIS = "extra_delay_millis";
+ static final String EXTRA_BASE_MULTIPLIER = "extra_base_multiplier";
+ static final String EXTRA_REMAINING_ATTEMPTS = "extra_remaining_attempts";
+
+ // Schedule an initial alarm to begin checking for a voicemail transcription result.
+ static void beginPolling(
+ Context context,
+ Uri voicemailUri,
+ String transcriptId,
+ long estimatedTranscriptionTimeMillis,
+ TranscriptionConfigProvider configProvider) {
+ long initialDelayMillis = configProvider.getInitialGetTranscriptPollDelayMillis();
+ long maxBackoffMillis = configProvider.getMaxGetTranscriptPollTimeMillis();
+ int maxAttempts = configProvider.getMaxGetTranscriptPolls();
+ double baseMultiplier =
+ ExponentialBaseCalculator.findBase(initialDelayMillis, maxBackoffMillis, maxAttempts);
+ Intent intent =
+ makeAlarmIntent(
+ context, voicemailUri, transcriptId, initialDelayMillis, baseMultiplier, maxAttempts);
+ // Add an extra to distinguish this initial estimated transcription wait from subsequent backoff
+ // waits
+ intent.putExtra(EXTRA_IS_INITIAL_ESTIMATED_WAIT, true);
+ VvmLog.i(
+ TAG,
+ String.format(
+ "beginPolling, check in %d millis, for: %s",
+ estimatedTranscriptionTimeMillis, transcriptId));
+ scheduleAlarm(context, estimatedTranscriptionTimeMillis, intent);
+ }
+
+ // Alarm fired, poll for transcription result on a background thread
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String transcriptId = intent.getStringExtra(EXTRA_TRANSCRIPT_ID);
+ VvmLog.i(TAG, "onReceive, for transcript id: " + transcriptId);
+ DialerExecutorComponent.get(context)
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(new PollWorker(context))
+ .onSuccess(this::onSuccess)
+ .onFailure(this::onFailure)
+ .build()
+ .executeParallel(intent);
+ }
+
+ private void onSuccess(Void unused) {
+ VvmLog.i(TAG, "onSuccess");
+ }
+
+ private void onFailure(Throwable t) {
+ VvmLog.e(TAG, "onFailure", t);
+ }
+
+ private static void scheduleAlarm(Context context, long delayMillis, Intent intent) {
+ PendingIntent alarmIntent =
+ PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ alarmMgr.set(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + delayMillis,
+ alarmIntent);
+ }
+
+ private static Intent makeAlarmIntent(
+ Context context,
+ Uri voicemailUri,
+ String transcriptId,
+ long delayMillis,
+ double baseMultiplier,
+ int remainingAttempts) {
+ Intent intent = new Intent(context, GetTranscriptReceiver.class);
+ intent.putExtra(EXTRA_VOICEMAIL_URI, voicemailUri);
+ intent.putExtra(EXTRA_TRANSCRIPT_ID, transcriptId);
+ intent.putExtra(EXTRA_DELAY_MILLIS, delayMillis);
+ intent.putExtra(EXTRA_BASE_MULTIPLIER, baseMultiplier);
+ intent.putExtra(EXTRA_REMAINING_ATTEMPTS, remainingAttempts);
+ return intent;
+ }
+
+ private static class PollWorker implements Worker<Intent, Void> {
+ private final Context context;
+
+ PollWorker(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public Void doInBackground(Intent intent) {
+ String transcriptId = intent.getStringExtra(EXTRA_TRANSCRIPT_ID);
+ VvmLog.i(TAG, "doInBackground, for transcript id: " + transcriptId);
+ Pair<String, TranscriptionStatus> result = pollForTranscription(transcriptId);
+ if (result.first == null && result.second == null) {
+ // No result, try again if possible
+ Intent nextIntent = getNextAlarmIntent(intent);
+ if (nextIntent == null) {
+ VvmLog.i(TAG, "doInBackground, too many failures for: " + transcriptId);
+ result = new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
+ } else {
+ long nextDelayMillis = nextIntent.getLongExtra(EXTRA_DELAY_MILLIS, 0L);
+ VvmLog.i(
+ TAG,
+ String.format(
+ "doInBackground, check again in %d, for: %s", nextDelayMillis, transcriptId));
+ scheduleAlarm(context, nextDelayMillis, nextIntent);
+ return null;
+ }
+ }
+
+ // Got transcript or failed too many times
+ Uri voicemailUri = intent.getParcelableExtra(EXTRA_VOICEMAIL_URI);
+ TranscriptionDbHelper dbHelper = new TranscriptionDbHelper(context, voicemailUri);
+ TranscriptionTask.recordResult(context, result, dbHelper);
+ return null;
+ }
+
+ private Pair<String, TranscriptionStatus> pollForTranscription(String transcriptId) {
+ VvmLog.i(TAG, "pollForTranscription, transcript id: " + transcriptId);
+ GetTranscriptRequest request = getGetTranscriptRequest(transcriptId);
+ TranscriptionClientFactory factory = null;
+ try {
+ factory = getTranscriptionClientFactory(context);
+ TranscriptionClient client = factory.getClient();
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_POLL_REQUEST);
+ GetTranscriptResponseAsync response = client.sendGetTranscriptRequest(request);
+ if (response == null) {
+ VvmLog.i(TAG, "pollForTranscription, no transcription result.");
+ return new Pair<>(null, null);
+ } else if (response.isTranscribing()) {
+ VvmLog.i(TAG, "pollForTranscription, transcribing");
+ return new Pair<>(null, null);
+ } 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);
+ }
+ } finally {
+ if (factory != null) {
+ factory.shutdown();
+ }
+ }
+ }
+
+ private GetTranscriptRequest getGetTranscriptRequest(String transcriptionId) {
+ Assert.checkArgument(transcriptionId != null);
+ return GetTranscriptRequest.newBuilder().setTranscriptionId(transcriptionId).build();
+ }
+
+ private @Nullable Intent getNextAlarmIntent(Intent previous) {
+ int remainingAttempts = previous.getIntExtra(EXTRA_REMAINING_ATTEMPTS, 0);
+ double baseMultiplier = previous.getDoubleExtra(EXTRA_BASE_MULTIPLIER, 0);
+ long nextDelay = previous.getLongExtra(EXTRA_DELAY_MILLIS, 0);
+ if (!previous.getBooleanExtra(EXTRA_IS_INITIAL_ESTIMATED_WAIT, false)) {
+ // After waiting the estimated transcription time, start decrementing the remaining attempts
+ // and incrementing the backoff time delay
+ remainingAttempts--;
+ if (remainingAttempts <= 0) {
+ return null;
+ }
+ nextDelay = (long) (nextDelay * baseMultiplier);
+ }
+ return makeAlarmIntent(
+ context,
+ previous.getParcelableExtra(EXTRA_VOICEMAIL_URI),
+ previous.getStringExtra(EXTRA_TRANSCRIPT_ID),
+ nextDelay,
+ baseMultiplier,
+ remainingAttempts);
+ }
+ }
+
+ private static TranscriptionClientFactory transcriptionClientFactoryForTesting;
+
+ static void setTranscriptionClientFactoryForTesting(TranscriptionClientFactory factory) {
+ transcriptionClientFactoryForTesting = factory;
+ }
+
+ private static TranscriptionClientFactory getTranscriptionClientFactory(Context context) {
+ if (transcriptionClientFactoryForTesting != null) {
+ return transcriptionClientFactoryForTesting;
+ }
+ return new TranscriptionClientFactory(context, new TranscriptionConfigProvider(context));
+ }
+}