summaryrefslogtreecommitdiff
path: root/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
diff options
context:
space:
mode:
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));
+ }
+}