Make sure that voicemail transcriptions are processed serially
Since we are now using the AlarmManager to decide when to poll
for voicemail transcriptions, we need to ensure that we don't
start transcribing a voicemail until polling for the previous
one ends.
This cl postpones a voicemail transcription task if there is an alarm
set to poll for a transcription result. After a transcription result
is received (or we give up) the db is scanned for any pending transcriptions
and the next one is started.
Bug: 70242961
Test: manual and updated unit tests
PiperOrigin-RevId: 181899975
Change-Id: I7b8fb696164980cf710aa58a79418c6954e2b4d2
diff --git a/java/com/android/voicemail/impl/AndroidManifest.xml b/java/com/android/voicemail/impl/AndroidManifest.xml
index e7ab581..1998d35 100644
--- a/java/com/android/voicemail/impl/AndroidManifest.xml
+++ b/java/com/android/voicemail/impl/AndroidManifest.xml
@@ -141,6 +141,10 @@
<receiver android:name="com.android.voicemail.impl.transcribe.GetTranscriptReceiver"
android:exported="false">
+ <intent-filter>
+ <action
+ android:name="com.android.voicemail.impl.transcribe.GetTranscriptReceiver.POLL_ALARM" />
+ </intent-filter>
</receiver>
</application>
</manifest>
diff --git a/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java b/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
index cc204ff..cbf1657 100644
--- a/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
+++ b/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java
@@ -23,11 +23,13 @@
import android.net.Uri;
import android.os.SystemClock;
import android.support.annotation.Nullable;
+import android.telecom.PhoneAccountHandle;
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.common.concurrent.ThreadUtil;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.voicemail.impl.VvmLog;
@@ -36,6 +38,7 @@
import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
import com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest;
import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus;
+import java.util.List;
/**
* This class uses the AlarmManager to poll for the result of a voicemail transcription request.
@@ -50,6 +53,9 @@
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";
+ static final String EXTRA_PHONE_ACCOUNT = "extra_phone_account";
+ static final String POLL_ALARM_ACTION =
+ "com.android.voicemail.impl.transcribe.GetTranscriptReceiver.POLL_ALARM";
// Schedule an initial alarm to begin checking for a voicemail transcription result.
static void beginPolling(
@@ -57,7 +63,9 @@
Uri voicemailUri,
String transcriptId,
long estimatedTranscriptionTimeMillis,
- TranscriptionConfigProvider configProvider) {
+ TranscriptionConfigProvider configProvider,
+ PhoneAccountHandle account) {
+ Assert.checkState(!hasPendingAlarm(context));
long initialDelayMillis = configProvider.getInitialGetTranscriptPollDelayMillis();
long maxBackoffMillis = configProvider.getMaxGetTranscriptPollTimeMillis();
int maxAttempts = configProvider.getMaxGetTranscriptPolls();
@@ -65,7 +73,13 @@
ExponentialBaseCalculator.findBase(initialDelayMillis, maxBackoffMillis, maxAttempts);
Intent intent =
makeAlarmIntent(
- context, voicemailUri, transcriptId, initialDelayMillis, baseMultiplier, maxAttempts);
+ context,
+ voicemailUri,
+ transcriptId,
+ initialDelayMillis,
+ baseMultiplier,
+ maxAttempts,
+ account);
// Add an extra to distinguish this initial estimated transcription wait from subsequent backoff
// waits
intent.putExtra(EXTRA_IS_INITIAL_ESTIMATED_WAIT, true);
@@ -77,9 +91,17 @@
scheduleAlarm(context, estimatedTranscriptionTimeMillis, intent);
}
+ static boolean hasPendingAlarm(Context context) {
+ Intent intent = makeBaseAlarmIntent(context);
+ return getPendingIntent(context, intent, PendingIntent.FLAG_NO_CREATE) != null;
+ }
+
// Alarm fired, poll for transcription result on a background thread
@Override
public void onReceive(Context context, Intent intent) {
+ if (intent == null || !POLL_ALARM_ACTION.equals(intent.getAction())) {
+ return;
+ }
String transcriptId = intent.getStringExtra(EXTRA_TRANSCRIPT_ID);
VvmLog.i(TAG, "onReceive, for transcript id: " + transcriptId);
DialerExecutorComponent.get(context)
@@ -101,7 +123,7 @@
private static void scheduleAlarm(Context context, long delayMillis, Intent intent) {
PendingIntent alarmIntent =
- PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ getPendingIntent(context, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmMgr.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
@@ -109,22 +131,46 @@
alarmIntent);
}
+ private static boolean cancelAlarm(Context context, Intent intent) {
+ PendingIntent alarmIntent = getPendingIntent(context, intent, PendingIntent.FLAG_NO_CREATE);
+ if (alarmIntent != null) {
+ AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ alarmMgr.cancel(alarmIntent);
+ alarmIntent.cancel();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
private static Intent makeAlarmIntent(
Context context,
Uri voicemailUri,
String transcriptId,
long delayMillis,
double baseMultiplier,
- int remainingAttempts) {
- Intent intent = new Intent(context, GetTranscriptReceiver.class);
+ int remainingAttempts,
+ PhoneAccountHandle account) {
+ Intent intent = makeBaseAlarmIntent(context);
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);
+ intent.putExtra(EXTRA_PHONE_ACCOUNT, account);
return intent;
}
+ private static Intent makeBaseAlarmIntent(Context context) {
+ Intent intent = new Intent(context.getApplicationContext(), GetTranscriptReceiver.class);
+ intent.setAction(POLL_ALARM_ACTION);
+ return intent;
+ }
+
+ private static PendingIntent getPendingIntent(Context context, Intent intent, int flags) {
+ return PendingIntent.getBroadcast(context.getApplicationContext(), 0, intent, flags);
+ }
+
private static class PollWorker implements Worker<Intent, Void> {
private final Context context;
@@ -158,9 +204,33 @@
Uri voicemailUri = intent.getParcelableExtra(EXTRA_VOICEMAIL_URI);
TranscriptionDbHelper dbHelper = new TranscriptionDbHelper(context, voicemailUri);
TranscriptionTask.recordResult(context, result, dbHelper);
+
+ // Check if there are other pending transcriptions
+ PhoneAccountHandle account = intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT);
+ processPendingTranscriptions(account);
return null;
}
+ private void processPendingTranscriptions(PhoneAccountHandle account) {
+ TranscriptionDbHelper dbHelper = new TranscriptionDbHelper(context);
+ List<Uri> inProgress = dbHelper.getTranscribingVoicemails();
+ if (!inProgress.isEmpty()) {
+ Uri uri = inProgress.get(0);
+ VvmLog.i(TAG, "getPendingTranscription, found pending transcription " + uri);
+ if (hasPendingAlarm(context)) {
+ // Cancel the current alarm so that the next transcription task won't be postponed
+ cancelAlarm(context, makeBaseAlarmIntent(context));
+ }
+ ThreadUtil.postOnUiThread(
+ () -> {
+ TranscriptionService.scheduleNewVoicemailTranscriptionJob(
+ context, uri, account, true);
+ });
+ } else {
+ VvmLog.i(TAG, "getPendingTranscription, no more pending transcriptions");
+ }
+ }
+
private Pair<String, TranscriptionStatus> pollForTranscription(String transcriptId) {
VvmLog.i(TAG, "pollForTranscription, transcript id: " + transcriptId);
GetTranscriptRequest request = getGetTranscriptRequest(transcriptId);
@@ -214,7 +284,8 @@
previous.getStringExtra(EXTRA_TRANSCRIPT_ID),
nextDelay,
baseMultiplier,
- remainingAttempts);
+ remainingAttempts,
+ previous.getParcelableExtra(EXTRA_PHONE_ACCOUNT));
}
}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java b/java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java
index a9a3722..24c07cc 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionDbHelper.java
@@ -22,11 +22,10 @@
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Build.VERSION_CODES;
+import android.os.Build;
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;
@@ -35,7 +34,7 @@
import java.util.List;
/** Helper class for reading and writing transcription data in the database */
-@TargetApi(VERSION_CODES.O)
+@TargetApi(Build.VERSION_CODES.O)
public class TranscriptionDbHelper {
@VisibleForTesting
static final String[] PROJECTION =
@@ -63,9 +62,8 @@
}
@WorkerThread
- @TargetApi(VERSION_CODES.M) // used for try with resources
Pair<String, Integer> getTranscriptionAndState() {
- Assert.checkState(BuildCompat.isAtLeastO());
+ Assert.checkState(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
Assert.isWorkerThread();
try (Cursor cursor = contentResolver.query(uri, PROJECTION, null, null, null)) {
if (cursor == null) {
@@ -84,9 +82,8 @@
}
@WorkerThread
- @TargetApi(VERSION_CODES.M) // used for try with resources
List<Uri> getUntranscribedVoicemails() {
- Assert.checkArgument(BuildCompat.isAtLeastO());
+ Assert.checkState(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
Assert.isWorkerThread();
List<Uri> untranscribed = new ArrayList<>();
String whereClause =
@@ -105,6 +102,25 @@
}
@WorkerThread
+ List<Uri> getTranscribingVoicemails() {
+ Assert.checkState(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
+ Assert.isWorkerThread();
+ List<Uri> inProgress = new ArrayList<>();
+ String whereClause = VoicemailCompat.TRANSCRIPTION_STATE + "=?";
+ String[] whereArgs = {String.valueOf(VoicemailCompat.TRANSCRIPTION_IN_PROGRESS)};
+ try (Cursor cursor = contentResolver.query(uri, PROJECTION, whereClause, whereArgs, null)) {
+ if (cursor == null) {
+ LogUtil.e("TranscriptionDbHelper.getTranscribingVoicemails", "query failed.");
+ } else {
+ while (cursor.moveToNext()) {
+ inProgress.add(ContentUris.withAppendedId(uri, cursor.getLong(ID)));
+ }
+ }
+ }
+ return inProgress;
+ }
+
+ @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 0f53003..c206c08 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
@@ -63,8 +63,7 @@
}
// Schedule a task to transcribe the indicated voicemail, return true if transcription task was
- // scheduled. If the PhoneAccountHandle is null then the voicemail will not be considered for
- // donation.
+ // scheduled.
@MainThread
public static boolean scheduleNewVoicemailTranscriptionJob(
Context context, Uri voicemailUri, PhoneAccountHandle account, boolean highPriority) {
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
index bb7aa5f..f6035fd 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
@@ -58,9 +58,24 @@
protected Pair<String, TranscriptionStatus> getTranscription() {
VvmLog.i(TAG, "getTranscription");
+ if (GetTranscriptReceiver.hasPendingAlarm(context)) {
+ // Don't start a transcription while another is still active
+ VvmLog.i(
+ TAG,
+ "getTranscription, pending transcription, postponing transcription of: " + voicemailUri);
+ return new Pair<>(null, null);
+ }
+
+ TranscribeVoicemailAsyncRequest uploadRequest = getUploadRequest();
+ VvmLog.i(
+ TAG,
+ "getTranscription, uploading voicemail: "
+ + voicemailUri
+ + ", id: "
+ + uploadRequest.getTranscriptionId());
TranscriptionResponseAsync uploadResponse =
(TranscriptionResponseAsync)
- sendRequest((client) -> client.sendUploadRequest(getUploadRequest()));
+ sendRequest((client) -> client.sendUploadRequest(uploadRequest));
if (cancelled) {
VvmLog.i(TAG, "getTranscription, cancelled.");
@@ -72,13 +87,14 @@
VvmLog.i(TAG, "getTranscription, upload error: " + uploadResponse.status);
return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
} else {
- VvmLog.i(TAG, "getTranscription, begin polling for result.");
+ VvmLog.i(TAG, "getTranscription, begin polling for: " + uploadResponse.getTranscriptionId());
GetTranscriptReceiver.beginPolling(
context,
voicemailUri,
uploadResponse.getTranscriptionId(),
uploadResponse.getEstimatedWaitMillis(),
- configProvider);
+ configProvider,
+ phoneAccountHandle);
// This indicates that the result is not available yet
return new Pair<>(null, null);
}